The goal of this document is to describe the recommended way of deploying a service to with Docker. With this procedure, the service is automatically deployed and updated on lund. For this documentation we assume that the service is simple enough to fit in one Docker container and that the service is accessible through HTTP.

NOTE: Please make sure to follow the process for new services. Setting up a Docker container or VM is just the technical part but in order to make the FSFE's technical infrastructure clear and maintainable, we need proper communication and documentation.

General Idea

We have one server that hosts our containers ( We have a Continuous Integration System, Drone, that builds and runs containers locally on it. Drone gets the commits from the git server, each commit triggers the creation of a "build" container, which in turn builds and runs containers with docker-compose. The reverse proxy container watches containers creation and create virtual hosts dynamically to route the HTTP traffic to the containers. Here is how it works:

1. Drone gets a message from Gitea (the git server) via a web hook. That can be commit, a tag, a push, etc. Drone sees the .drone.yml file of the repository the event is coming from, and execute the instructions in it.

2. The instructions in .drone.yml creates a container called docker:dind (Docker in Docker), then installs docker-compose and run 'docker-compose up --build -d'

3. docker-compose builds images and run container. As the container is "Docker in Docker', it controls the Docker daemon from lund directly, outside of the container.

4. As a final step, once the container of the application to be deployed is running, it gets connected to the "bridge" network so the reverse proxy container can route the traffic from the outside to the application.

Below are the configuration needed to deploy the application container on lund.


The Dockerfile that builds the service should have 4 main items:

1. Selection of the base image with a FROM statement.

2. List of the ports that will be exposed so Docker knows what ports are used in the container. Use the EXPOSE statement

3. The actual build of the service. Install the service dependencies and the service itself

4. A CMD statement so the service is run automatically when the container starts.

An example Dockerfile is provided below:

# Base image selection
FROM debian:9

# List of ports that will be exposed to the outside world

# Installing the services and dependencies
RUN [ "apt-get", "-q", "update" ]
RUN [ "apt-get", "-qy", "--force-yes", "upgrade" ]
RUN [ "apt-get", "-qy", "--force-yes", "dist-upgrade" ]
RUN [ "apt-get", "install", "--no-install-recommends", "-qy", "--force-yes", \
      "ca-certificates", \
      "perl", \
      "git", \
      "uuid-dev", \
      "sqlite3", \
      "build-essential", \
      "cpanminus" ]
RUN [ "apt-get", "clean" ]
RUN [ "rm", "-rf", "/var/lib/apt/lists/*", "/tmp/*", "/var/tmp/*" ]
RUN ["cpanm", "--notest", "DBI", "DBD::SQLite", "Mojolicious", "UUID" ]
RUN adduser --quiet --disabled-password --shell /bin/bash --home /home/perlscript --gecos "User" perlscript
USER perlscript
WORKDIR /home/perlscript
RUN git clone
WORKDIR /home/perlscript/mailman-reconfirm

# CMD to run the service on container start
CMD perl daemon -l

Here is the Dockerfile reference.

Docker compose file

Next, we need to create an docker-compose file. The docker-compose.yml file is responsible for building the images (with the Dockerfile) and running the containers. To run the container, there are mandatory options:

An example is provided below:

version: '3'
    container_name: mailman-reconfirm
    build: .
    image: mailman-reconfirm
    restart: always
      - 3000
      - /srv/mailman-reconfirm/data:/home/perlscript/mailman-reconfirm/data/:rw

  # Connect the container which exposes the service to the 'bridge' network as
  # this is where the reverse proxy is
    image: docker:dind
      - /var/run/docker.sock:/var/run/docker.sock
      - mailman-reconfirm
    command: /bin/sh -c 'docker network connect bridge mailman-reconfirm'

Here is the docker-compose file reference.

Drone configuration

Drone is the Continuous Integration and Continuous Delivery software we use. We need to tell Drone to build the image and run the container.

Add this file to your project and call it .drone.yml:

      # Run docker compose
      - docker-compose up --build -d
    image: tmaier/docker-compose
      - /var/run/docker.sock:/var/run/docker.sock
       branch: master
       event: [push, tag, deployment]

This uses the Docker in Docker image (which is based on the alpine image) to run the docker-compose file inside a container. As the image is "Docker in Docker" and has the docker socket (/ shared as a mountpoint, it controls the docker daemon directly on Lund, outside of the container. The command docker-compose up -d --build means that docker-compose will build the images, run the containers and exit.

In the current Drone version, you have to make the repository Trusted in the repository's Drone settings.


You can do a test commit to see if it deploys the container correctly.

git commit --allow-empty -m 'Trigger build'

How to use Drone secrets

Drone secrets allow you to securely insert secrets in your Docker images.

Add the secret in the Drone configuration

Make sure that Drone is activated for the repository, then connect to lund as root and run the following commands:

cd ~
./drone secret add --repository <repository path> --name <SECRET_NAME> --value <VALUE>

Add the secret to the .drone.yml file with the secrets keyword:

    image: docker:dind
    secrets: [ SECRET_NAME ]

Add the secret in docker-compose as a build argument

Instead of providing only the build context, provide the arguments as well.

Change the line:

    build: .


       context: .

This will add the --build-arg flag to the docker build command.

Use the secret in the Dockerfile

Then you can add the argument in the Dockerfile by adding this line:


SECRET_NAME is considered as a environment variable for the image build and therefore can be used with $SECRET_NAME.

Note: This environment variable will not exist in the image, it's only available during the build.

How to use LDAP

If you want your container to authenticate users via the FSFE's LDAP, you have to create a TLS certificate for it. Authentication, e.g. against a LDAP group, is only possible with a) the password of the LDAP groupreader, and b) valid certificates against the LDAP CA.

To establish an encrypted connection with the LDAP server, you can use a small Docker container with stunnel running in it, so your application does not necessarily have to provide LDAP over TLS certificates.

If you want to create a new service with LDAP connection, these are the rough steps:

  1. Create a LDAP certificate. This is a highly sensible step, so ask the System Hacker coordinators
  2. Follow the steps as explained on the stunnel repo


Containers that have the VIRTUAL_HOST environment variable or the fsfe-monitoring: "true" label gets monitored by the container monitoring. See here for more details.

TechDocs/TechnicalProcesses/DockerDeployment (last edited 2020-06-03 14:31:45 by max.mehl)