Production Operations on a Single Server · 4/10

Docker Compose Production Readiness Checklist

Part of:Deploy your own AI stack · Step 4/11 →
What will this help you do?

Before moving a working Compose project into production, check image versions, builds, ports, internal networks, persistent volumes, health checks, restarts, and rollback.

Who is it for?

Readers working through “Deploy your own AI stack”.

Before you start

Check your server or local environment, access rights and existing backups.

Updated

On this page

Starting containers is only the beginning

A local Compose project may use source-code mounts, development ports, latest images, and default passwords. Those conveniences become uncertainty in production. Changing an environment variable to production is not enough. You need to know where images come from, where data lives, how containers communicate, how failure is detected, and how to return to a previous version.

Keep a base compose.yaml and use compose.production.yaml for production differences. This makes the boundary reviewable without maintaining two full files that slowly diverge.

Use images you can identify and retrieve again

Pin a version or immutable digest, such as myapp:2026-08-14, rather than relying on latest, whose contents change over time. If you build on the server, pin the base image and use a lockfile. Check that the build context does not include .env, databases, or backups. A controlled build followed by pulling the same image on the server can be easier to reproduce.

Record the image ID and release version. A rollback needs the last verified image, not a fresh build of old code; base images and dependency sources may have changed in the meantime.

Publish only the port the reverse proxy needs

When Caddy runs on the host, bind the application port to loopback:

yaml
services:
  app:
    image: registry.example.com/myapp:2026-08-14
    ports:
      - "127.0.0.1:3000:3000"
    restart: unless-stopped

The database does not need a published ports entry; containers can reach it by service name on an internal Compose network. EXPOSE in an image is not the same as publishing a host port. Check actual listeners with ss -lntp and docker compose ps.

Know exactly which data must survive container replacement

List every writable location: database, uploads, search indexes, queues, and generated application files. Give bind mounts explicit host paths and permissions. For named volumes, document where their data is and how to back it up. Removing a container should not remove durable data, but docker compose down -v removes volumes; a production runbook must not treat it as routine cleanup.

Mount read-only configuration separately from persistent data. If containers run as non-root users, set directory ownership ahead of time. Do not respond to a permission error by making a data directory world-writable.

A health check should test a useful capability

A running process may still be unable to serve requests. Provide a lightweight /health endpoint that confirms essential initialization without running an expensive full-table database query every few seconds. Set sensible interval, timeout, retries, and startup grace period:

yaml
healthcheck:
  test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/health"]
  interval: 30s
  timeout: 5s
  retries: 3
  start_period: 20s

Check that the image actually contains wget; otherwise use a tool it does contain. Dependency ordering may reduce startup races, but the application still needs to handle unavailable dependencies and retries.

Inspect the rendered configuration before starting

bash
docker compose -f compose.yaml -f compose.production.yaml config
docker compose -f compose.yaml -f compose.production.yaml pull
docker compose -f compose.yaml -f compose.production.yaml up -d
docker compose ps
docker compose logs --tail=100 app

The config output reveals final ports, mounts, and resolved variables. Inspect it without copying secrets into an issue or chat. After starting, test the local health endpoint, the Caddy domain, and an essential application route.

Plan updates and rollback before the first release

Back up data before deployment, pull the new image, wait for health, and then switch or continue serving traffic. If the new version fails, restore the previous image tag and run up again rather than trying to rebuild an old Git commit during an incident. Check database migrations for backward compatibility. A data change that cannot be rolled back may require an expand-then-switch-then-clean-up sequence.

You are ready when images are identifiable, public ports are minimal, persistent data is listed and backed up, health checks really work, the final configuration has been reviewed, and the previous image remains available. Next, protect the part most likely to leak through files or logs: production secrets.

Sources

  1. Use Compose in productionDocker Docs
  2. Best practices for working with environment variables in Docker ComposeDocker Docs

Next steps

Continue with the next useful task; you do not need to read everything at once.

  1. Install Ollama →
  2. Deploy on One Server with Health Checks and a Rollback Plan →
  3. Deploy Open WebUI with Docker and Persistent Storage →
  4. Deploy Dify with Docker Compose: A Production-minded Guide →
Back to the project path →
RESOURCES I USE · REFERRAL

Two services to compare when you are ready to launch

This is not an automated ranking, and neither service is necessary for everyone. These are services I use, with the use case and limitations kept visible.

Cloud server · Used for early projects

RainYun

A practical candidate for a website or small service. Choose by user region, configuration and measured workload rather than the lowest headline price.

Referral disclosure: these links contain my referral information. I may receive a platform benefit if you sign up or order, at no additional charge from AIOOS. Check the order page for current pricing, availability, regions and terms. Read the full affiliate disclosure