Lập trình

Docker Compose for Consistent Development Environments

“It works on my machine” usually means the development environment is underspecified. Different runtime, database, extension, or environment-variable versions can make onboarding slow and defects difficult to reproduce.

Docker Compose cho môi trường phát triển nhất quán

“It works on my machine” usually means the development environment is underspecified. Different runtime, database, extension, or environment-variable versions can make onboarding slow and defects difficult to reproduce.

Docker Compose describes a multi-service application in version-controlled configuration. The goal is not to containerize everything indiscriminately, but to give the team one repeatable way to build, run, reset, and test the stack.

Start with the services you need

services:
  app:
    build:
      context: .
      target: development
    ports:
      - "8080:8080"
    env_file:
      - .env.docker
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    volumes:
      - db-data:/var/lib/postgresql/data
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  db-data:

secrets:
  db_password:
    file: ./.secrets/db_password

Pin a major or exact image version according to project policy. The latest tag makes environments change unexpectedly and complicates reproduction.

depends_on does not always mean ready

A database container may be running while initialization is still in progress. Compose waits for readiness only when a dependency uses condition: service_healthy. A health check should test actual service capability, not merely process existence.

The application should still retry short-lived connection failures because a dependency may restart after the full stack is healthy.

Choose the right mount

TypeBest forTrade-off
Bind mountSource code edited on the hostHost filesystem and permission differences
Named volumeDatabase data and dependency cachesManaged separately by Docker
tmpfsTemporary data and fast testsLost when the container stops

Avoid mounting the entire repository without thought. Sharing host node_modules across operating systems can break native binaries. Use a named dependency volume or install dependencies inside the image.

Use Compose Watch for a faster loop

services:
  app:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: ./package.json
docker compose watch

Watch can synchronize source files and rebuild when dependency manifests change. Ignore generated output, dependencies, and secrets.

Separate common and local-only configuration

Keep common service definitions in compose.yaml. Add debug ports, bind mounts, and local tools in compose.override.yaml. This reduces the risk of development settings reaching production.

docker compose config

This command renders the merged, interpolated configuration. Running it in CI catches invalid YAML, missing variables, and unexpected overrides.

Keep secrets out of Git and image layers

  • Commit an .env.example containing names and placeholders.
  • Protect local secret files with appropriate permissions.
  • Do not write secrets into a Dockerfile.
  • Never reuse production credentials locally.
  • Use Compose secrets or the deployment platform's secret manager.

Build a cache-friendly development image

FROM node:22-alpine AS development
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

COPY . .
CMD ["npm", "run", "dev", "--", "--host", "0.0.0.0"]

Copy dependency manifests before source code to preserve the expensive install layer. Add a .dockerignore for Git data, logs, build output, host dependencies, and secret files.

Standardize common commands

docker compose up --build
docker compose exec app npm test
docker compose logs -f app
docker compose down
docker compose down -v

down -v deletes named volumes and local data. Put destructive resets behind a clearly named project script.

Do not force Compose to mirror production exactly

Compose is excellent for local development, CI, and single-host environments. Production may use orchestration or managed services. Keep runtime versions, dependencies, configuration contracts, migrations, and health behavior consistent; infrastructure details do not have to be identical.

Repository checklist

  • Commit the Compose file, Dockerfile, and dependency lockfile.
  • Pin image versions according to policy.
  • Add meaningful health checks and application-level connection retry.
  • Choose mounts intentionally for source, dependencies, and data.
  • Keep secrets out of Git and image layers.
  • Provide one-step commands for start, test, logs, and reset.
  • Validate merged configuration with docker compose config in CI.

References

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.