Configure Docker apps

Important

When using Docker, configure an external firewall.

Note

This guide works with existing images. See the Software Development Handbook for how to build images from a Dockerfile using GitHub Actions.

The docker_apps state file performs common operations for apps deployed using Docker Compose. In your app’s state file, include it with:

include:
  - docker_apps

If you already have an include state, add docker_apps to its list.

This will:

  • Install the Docker service

Configure Docker

One-time setup

Do this only once per server.

Add - docker to the server’s entry in the pillar/top.sls file.

In the server’s Pillar file, add, for example:

docker:
  user: deployer
  syslog_logging: True

This will:

  • Add a non-root user to the docker group

  • Configure container logs to be written to /var/log/docker-custom/CONTAINER_NAME.log, with rotation

Add Docker Compose file

Create an {app}.yaml file in the salt/docker_apps/files directory. For example:

services:
  web:
    image: "ghcr.io/open-contracting/myrepo:latest"
    restart: unless-stopped

Validate the file, for example:

docker compose config -q salt/docker_apps/files/registry.yaml

Stateful containers

Containers are designed to be interrupted at any time, whereas stateful services like PostgreSQL and RabbitMQ can fail in such conditions. Instead, run these on the host, where they are easier to operate with high reliability.

One-off commands

To run a one-off command, like a database migration, use docker compose run on the command line, instead of creating a one-time container. See Docker tasks for examples.

If you need to run a scheduled task in a cron job, use docker compose --progress=quiet run --rm --name my-app-cron, replacing my-app. If needed, change the log level by adding -e LOG_LEVEL=WARNING, for example.

Confirm the meaning of a cron expression using Cronhub.

Shared configuration

To share configuration between services, you can use this pattern:

x-shared: &shared
  image: "ghcr.io/open-contracting/myrepo:latest"
  restart: unless-stopped

services:
  web:
    <<: *shared
  worker:
    <<: *shared
    command: "python -m worker"
    deploy:
      replicas: 2

Reference:

Configure Docker app

In the server’s Pillar file, add, for example:

docker_apps:
  myapp:
    target: mytarget
    env:
      FATHOM_ANALYTICS_ID: ABCDEFGH

In the server’s private Pillar file, add, for example:

docker_apps:
  myapp:
    env:
      SENTRY_DSN: https://1234567890abcdef1234567890abcdef@o123456.ingest.sentry.io/1234567890123456

This will create files in the /data/deploy/mytarget directory:

  • docker-compose.yaml, with the contents of the salt/docker_apps/files/myapp.yaml file

  • .env, containing the values under the env key

To reuse a Docker Compose file, specify the configuration, which otherwise defaults to the app’s name. For example:

docker_apps:
  cove_ocds:
    configuration: cove  # default cove_ocds
    target: cove-ocds
  cove_oc4ids:
    configuration: cove  # default cove_oc4ids
    target: cove-oc4ids

See also

Environment variables for Django projects

Reference:

Use host services

To connect to the host’s services, like PostgreSQL or RabbitMQ, add to the Docker Compose file:

services:
  web:
    image: "ghcr.io/open-contracting/myrepo:latest"
    restart: unless-stopped
    extra_hosts:
      - "host.docker.internal:host-gateway"

Then, under the env key in the server’s Pillar file, use host.docker.internal instead of localhost. For example:

docker_apps:
  myapp:
    target: mytarget
    env:
      DATABASE_URL: "postgresql://USERNAME:PASSWORD@host.docker.internal:5432/name"

Reference:

Map a port

If the Dockerfile exposes a port, in the server’s Pillar file, add, for example:

docker_apps:
  myapp:
    target: mytarget
    port: 8001
    env:
      MYVAR: myvalue

This makes it easier for multiple Docker Compose files to refer to the port.

Then, in the Docker Compose file, add, for example:

services:
  web:
    image: "ghcr.io/open-contracting/myrepo:latest"
    restart: unless-stopped
    ports:
      - {{ entry.port }}:8000

Alternatively, if port is already set in the context of an Apache site, do: {{ site.port }}

Add a bind mount

See the last step for Bind mounts in the Software Development Handbook.

Configure Apache

Apache is used as a reverse proxy to any web servers in the Docker containers. See Configure Apache. The configuration can simply be ProxyPass directives.

Additional files

Setup

Create additional files needed to setup the service (e.g. SQL migrations) in the /data/deploy/TARGET/files directory.

Use

Create additional files needed to use the service (e.g. sudoer binaries) in the /opt or /opt/TARGET directory.