Cubis Engineers

Compose Applications

Define a multi-container application with explicit health, network, secret, and storage boundaries.

Application deliveryIntermediateUpdated Aug 13, 2026dockercomposenetworkingvolumessecrets

Compose describes services, networks, volumes, configs, and secrets in one file. Use the current Compose Specification and the docker compose command; a top-level legacy version field is not required.

Define the application

compose.yaml
services:
  app:
    image: registry.example.com/cubis-api:${APP_VERSION}
    restart: unless-stopped
    init: true
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: "5432"
      DATABASE_NAME: cubis
      DATABASE_USER: cubis
      DATABASE_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    ports:
      - "127.0.0.1:3000:3000"
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 20s
    read_only: true
    tmpfs:
      - /tmp
    security_opt:
      - no-new-privileges:true
    networks:
      - backend

  db:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: cubis
      POSTGRES_USER: cubis
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - db_data:/var/lib/postgresql/data
    networks:
      - backend

secrets:
  db_password:
    file: ./secrets/db_password

volumes:
  db_data:

networks:
  backend:
    internal: true

The database has no host port. The application reaches it as db:5432 through Compose service discovery. This example assumes the application reads DATABASE_PASSWORD_FILE; adapt the setting to the application’s documented secret-loading mechanism. The application port is bound to host loopback so a host-managed reverse proxy can reach it without exposing it on every interface.

The secret file must not be committed. Compose mounts it into the container, but local file permissions and access to the Docker host remain part of the security boundary.

Validate before starting

Terminal
export APP_VERSION=2026.08.13-3f28c1a
docker compose config --quiet
docker compose config --images
docker compose pull
docker compose up -d --remove-orphans
docker compose ps

Review an unfamiliar Compose file before running it. Bind mounts, host networking, devices, privileged mode, and the Docker socket can give a container extensive access to the host.

Work with the running project

Terminal
docker compose logs --tail=100 app
docker compose exec app id
docker compose exec app wget -qO- http://127.0.0.1:3000/health
docker compose exec db pg_isready -U cubis -d cubis
docker compose top

Use service names, not container IP addresses. A recreated container can receive a different IP while retaining the same DNS name.

Understand volume lifecycle

Terminal
docker compose stop
docker compose down
docker volume ls

docker compose down removes project containers and networks but keeps named volumes unless --volumes is provided. Treat down --volumes as destructive when a volume contains data.

Health is not readiness for every dependency

A container health check reports the command result inside that container. It does not prove the complete user path works. Verify the public endpoint, critical dependency access, and a representative application operation after deployment.

References

On this page