Continuous deployment of SAFE apps on linux server

For some time I was looking for a solution for how to do continuous deployment of my SAFE stack projects to the server. My needs are:

  • automagically deploy a new version after push to master branch

  • support multiple apps on one server with different URLs (,

My final solution consists of:

  • build as a Docker image

  • GitLab CI / CD for building image and push it to the registry

  • Watchtower - docker app for automatically updating to new version of docker images

  • Nginx - reverse proxy for serving multiple apps

Repository side

Full build with Docker

Following 2-phase Dockerfile do a full build of SAFE app and run it.

FROM vbfox/fable-build:stretch AS builder

WORKDIR /build

RUN dotnet tool install fake-cli -g
ENV PATH="${PATH}:/root/.dotnet/tools"

# Package lock files are copied independently and their respective package
# manager are executed after.
# This is voluntary as docker will cache images and only re-create them if
# the already-copied files have changed, by doing that as long as no package
# is installed or updated we keep the cached container and don't need to
# re-download.

# Initialize node_modules
COPY package.json yarn.lock ./
RUN yarn install

# Initialize paket packages
COPY paket.dependencies paket.lock ./
COPY .paket .paket
RUN mono .paket/paket.exe restore

# Copy everything else and run the build
COPY . ./
RUN rm -rf deploy
RUN fake run build.fsx --target Bundle

FROM microsoft/dotnet:2.1-aspnetcore-runtime-alpine
COPY --from=builder /build/deploy ./
WORKDIR /app/Server
ENTRYPOINT ["dotnet", "Server.dll"]

Gitlab CI / CD and docker registry

This .gitlab-ci.yml config builds the docker image using the above Dockerfile, and pushes it to the built-in Docker container registry. Docker image address will be<user-name>/<repo-name>:<branch-name>.

image: docker:stable
  - docker:dind

  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2

  - docker info
  - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY

  stage: build
    - docker build -f build.Dockerfile -t $IMAGE_TAG .
    - docker push $IMAGE_TAG

Server side

Docker run options

On my server, I am using the following command to run the docker image: docker run -p <port>:8085 --restart unless-stopped --name <name> -d <docker-image>

For each app, I am using a different port.


Watchtower is a great docker app that watches over your other running docker images, and when a new version is uploaded, that docker image is updated and restarted.

Using watchtower is simple, just run once:

docker run -d --name watchtower -v /var/run/docker.sock:/var/run/docker.sock --restart unless-stopped v2tec/watchtower

Multiple applications on one server with Nginx

Each app running on a different port can be mapped to url using reverse proxy with Nginx. My configuration may look like this:


server {
        listen 80;
        location /app1/ {
             proxy_pass http://localhost:9001/;
        location /api/app1/ {
             proxy_pass http://localhost:9001/api/app1/;
        location /app2/ {
             proxy_pass http://localhost:9002/;
        location /api/app2/ {
             proxy_pass http://localhost:9002/api/app2/;

I used two entries for each app, one for the main app site, and the second for API that SAFE uses for communication between its client and server part. This way the app works in local and production environments.

End note

A sample of the working repository with my setup can be found here: safe-template.

At the moment, I am using this pipeline for 2 projects now: