Deploying Docker Compose

Written by

in

Deploying Docker Compose: A Practical Guide for Production Teams

Disclosure: This post contains one or more links to providers we have a real, registered affiliate/referral relationship with. We may earn a commission at no extra cost to you if you sign up through them.

Deploying Docker Compose to a real server involves more than running docker compose up and hoping for the best. This guide walks through the practical steps of deploying Docker Compose applications reliably, covering configuration, secrets, networking, and the day-to-day operational habits that separate a fragile setup from a stable one.

Docker Compose was originally built for local development, but it has become a genuinely useful tool for deploying small-to-medium production workloads on a single host or a small cluster. Whether you’re standing up a self-hosted automation stack, a database-backed web app, or an internal tool, deploying Docker Compose correctly means thinking about restart policies, environment separation, logging, and rollback strategy from the start rather than retrofitting them later.

Why Deploying Docker Compose Still Makes Sense in 2026

Kubernetes gets most of the attention in infrastructure conversations, but not every workload needs an orchestrator with dozens of moving parts. For a single VPS, a small team, or a project that doesn’t need horizontal autoscaling, deploying Docker Compose is often the more maintainable choice. It keeps the entire application topology in one readable file, requires no control-plane overhead, and lets a single engineer reason about the whole system without needing a dedicated platform team.

That said, Compose isn’t a toy. Production use requires the same discipline you’d apply to any deployment: version-controlled configuration, secrets kept out of the repository, health checks, and a repeatable rollout process. If your workload later outgrows a single host, it’s worth reading a comparison like Kubernetes vs Docker Compose to understand when the tradeoff actually flips in Kubernetes’ favor.

When a Single Host Is Enough

A single well-specced VPS can comfortably run a Compose stack for most internal tools, small SaaS products, and automation platforms — things like a self-hosted n8n instance, a WordPress site, or a small API backend. The deciding factor isn’t request volume alone; it’s whether you need automatic failover across multiple machines. If one host going down for a few minutes during a restart is acceptable, Compose is usually sufficient.

When to Reconsider

If you need zero-downtime rolling deployments across multiple nodes, built-in service discovery across a cluster, or automated horizontal scaling based on load, you’re better served by an orchestrator. Deploying Docker Compose past that point usually means bolting on scripts to replicate what Kubernetes or Nomad already do natively — at which point the “simplicity” argument for Compose no longer holds.

Preparing Your Compose File for Production

Before deploying Docker Compose to any server, the compose.yaml file itself needs to look different from a local dev version. Development compose files often bind-mount source code, expose every port to 0.0.0.0, and skip restart policies entirely. None of that belongs in production.

A production-ready Compose file typically includes:

  • Explicit image tags (never latest) so redeploys are reproducible
  • A restart: unless-stopped or restart: always policy on every long-running service
  • Named volumes instead of bind mounts for persistent data
  • Health checks so Docker (and your monitoring) can tell when a container is actually ready
  • Resource limits (deploy.resources.limits or the legacy mem_limit/cpus fields) to stop one container from starving the host
  • Here’s a minimal example of a production-oriented Compose file for a small web app with a database:

    services:
      web:
        image: registry.example.com/myapp:1.4.2
        restart: unless-stopped
        depends_on:
          db:
            condition: service_healthy
        environment:
          - DATABASE_URL=postgres://app:${DB_PASSWORD}@db:5432/app
        ports:
          - "127.0.0.1:8080:8080"
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
          interval: 30s
          timeout: 5s
          retries: 3
    
      db:
        image: postgres:16
        restart: unless-stopped
        environment:
          - POSTGRES_USER=app
          - POSTGRES_PASSWORD=${DB_PASSWORD}
          - POSTGRES_DB=app
        volumes:
          - db_data:/var/lib/postgresql/data
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U app"]
          interval: 10s
          timeout: 5s
          retries: 5
    
    volumes:
      db_data:

    Note that the web port binds only to 127.0.0.1, with a reverse proxy (Nginx, Caddy, or Traefik) handling public traffic and TLS termination. Exposing application containers directly on 0.0.0.0 is a common mistake when deploying Docker Compose to a public-facing VPS. For a Postgres-specific walkthrough, the Postgres Docker Compose setup guide covers backup and tuning details this example skips.

    Pinning Versions and Managing Image Tags

    Using latest for any image in a deployment is a reliable way to introduce surprise breakage. When a base image updates, your next docker compose pull can silently change the application behavior, dependencies, or even the entrypoint. Pin exact versions (postgres:16.4, not postgres:latest) and treat a version bump as a deliberate, reviewed change, not something that happens automatically on a routine deploy.

    Handling Secrets and Environment Variables Correctly

    Deploying Docker Compose safely means never committing real credentials to version control. The most common approach is a .env file, read automatically by Compose and referenced with ${VARIABLE} syntax in the compose file, combined with .gitignore to keep it out of the repository.

    For anything more sensitive — API keys, database passwords, TLS private keys — Compose’s native secrets: block (backed by files rather than environment variables) reduces the risk of a secret leaking into docker inspect output or process listings. Environment variables are visible to any process that can read /proc/<pid>/environ on the host, while file-based secrets mounted at /run/secrets/ are scoped more tightly.

    Two site resources go into more depth here:

  • Docker Compose Env: Manage Variables the Right Way covers .env file precedence rules and common variable-substitution pitfalls.
  • Docker Compose Secrets: Secure Config Management Guide covers the secrets: block itself, including Docker Swarm’s encrypted secret store versus the plain-file approach used in standalone Compose.
  • Separating Environments Cleanly

    A common pattern when deploying Docker Compose across dev, staging, and production is to use Compose’s file-layering feature: a base compose.yaml with shared service definitions, plus compose.prod.yaml and compose.dev.yaml overrides that supply environment-specific values.

    docker compose -f compose.yaml -f compose.prod.yaml up -d

    This avoids maintaining three near-duplicate files and keeps the differences between environments explicit and reviewable in a diff.

    Choosing and Provisioning a Server

    Deploying Docker Compose to production starts with picking infrastructure that matches the workload. A small automation stack or single-app deployment usually needs 2-4 vCPUs, 4-8 GB of RAM, and enough disk for logs, images, and any database volumes — specifics depend heavily on your actual services, so size based on your own container resource usage rather than a generic recommendation. Providers like DigitalOcean, Hetzner, and Vultr all offer VPS tiers suited to running a Compose stack, with straightforward Docker-ready images available at provisioning time.

    Once the server exists, install Docker Engine and the Compose plugin per the official Docker installation instructions, and confirm the daemon is running before deploying anything.

    Firewalling and Network Exposure

    Deploying Docker Compose without firewall rules is one of the more common production mistakes. Docker manipulates iptables directly to publish container ports, which means a UFW rule blocking a port at the OS level can still be bypassed by a container’s own port mapping. Bind container ports to 127.0.0.1 where a reverse proxy is used, and confirm with iptables -L -n (not just ufw status) that only the ports you intend are actually reachable from outside.

    The Deployment Workflow Itself

    A repeatable deployment workflow for Compose generally follows the same shape regardless of what the application does:

    1. Pull or build the latest tagged image
    2. Copy the updated compose.yaml/override files and .env to the server (or check out from git)
    3. Run docker compose pull to fetch new images
    4. Run docker compose up -d to recreate only the containers whose configuration changed
    5. Verify health checks pass and application logs look clean
    6. Roll back to the previous image tag if anything looks wrong

    # on the target server, inside the project directory
    git pull origin main
    docker compose pull
    docker compose up -d
    docker compose ps

    docker compose up -d is idempotent — it only recreates containers whose image or configuration actually changed, leaving untouched services running. This is one of the underrated strengths of deploying Docker Compose over a naive “stop everything, start everything” script, since it minimizes downtime for services that didn’t change.

    Zero-Downtime Considerations

    Standalone Compose doesn’t have native rolling-update support the way Swarm or Kubernetes does — recreating a container means a brief gap between the old container stopping and the new one becoming healthy. For workloads where even a few seconds of downtime matters, options include running two instances behind a reverse proxy and swapping traffic manually, or migrating to Docker Swarm mode (which understands Compose files natively) for its update_config rolling-deploy behavior. For most internal tools and low-traffic sites, a few seconds of downtime during a deploy is an acceptable tradeoff for the simplicity Compose provides.

    Rebuilding After Code or Dockerfile Changes

    If you’re deploying Docker Compose for an application you build from source rather than pulling a prebuilt image, remember that docker compose up -d alone won’t pick up Dockerfile changes — you need an explicit rebuild step. The Docker Compose Rebuild guide covers exactly when --build is required versus when Compose can detect the change on its own, and the difference between Dockerfile and Docker Compose responsibilities is worth understanding if you’re new to the build-vs-orchestration split.

    Monitoring, Logging, and Troubleshooting After Deployment

    A Compose deployment isn’t done once docker compose up -d exits successfully — you need visibility into whether it keeps running correctly. docker compose logs -f is the first troubleshooting step for almost any issue, and docker compose ps quickly shows which containers are unhealthy or restarting in a loop.

    By default, Docker’s json-file log driver has no size cap, which means a noisy service can quietly fill a disk over weeks of uptime. Set log rotation explicitly:

    services:
      web:
        image: registry.example.com/myapp:1.4.2
        logging:
          driver: json-file
          options:
            max-size: "10m"
            max-file: "3"

    For a deeper look at reading and filtering logs across multiple services, see the Docker Compose Logs debugging guide. If you’re running a cache or queue alongside your main application, the Redis Docker Compose setup guide is a useful companion for that specific service.

    Backups Before Every Deploy

    Any deployment that includes a database should have an automated backup step that runs before a deploy touches that service — a pg_dump or equivalent, stored off-host. This is separate from application deployment but should be part of the same runbook: a bad migration during a Compose deploy is far less stressful to recover from when a known-good dump exists from minutes earlier.


    Recommended: Ready to put this into practice? DigitalOcean is a tool we use for exactly this, and we have a real, disclosed affiliate relationship with them.

    FAQ

    Is Docker Compose suitable for production, or only for local development?
    Docker Compose is used in production for many single-host or small-scale deployments. It lacks built-in multi-node orchestration, automatic failover, and rolling updates across a cluster, but for workloads that fit on one server, deploying Docker Compose in production is a common and reasonable choice as long as you apply the same operational discipline (restart policies, health checks, secrets management) you would with any other deployment method.

    What’s the difference between docker compose up and docker compose up -d?
    docker compose up runs in the foreground, streaming logs to your terminal and stopping the stack when you press Ctrl+C. docker compose up -d runs the containers detached in the background, which is what you want for any real deployment, since the process needs to survive after your SSH session ends.

    How do I roll back a Compose deployment if something breaks?
    Keep the previous image tag referenced somewhere (in your deploy script’s history, a git tag, or your CI/CD pipeline’s build log), then update the image: line back to that tag and run docker compose up -d again. This is why pinning explicit version tags instead of latest matters — without a known-good tag to revert to, rollback becomes guesswork.

    Do I need Docker Swarm mode to deploy Docker Compose files in production?
    No. Standard Compose runs perfectly well against a single Docker Engine daemon without Swarm being initialized. Swarm mode becomes relevant only if you need multi-node scheduling or built-in rolling updates — Compose files are largely compatible with Swarm’s docker stack deploy if you outgrow single-host limits later, which is one reason Compose remains a reasonable starting point.

    Conclusion

    Deploying Docker Compose to production is straightforward in principle but requires the same operational rigor as any other deployment method: pinned image versions, externalized secrets, health checks, log rotation, and a documented rollback path. For workloads that fit comfortably on a single host, Compose offers a genuinely simpler operational model than a full orchestrator, without sacrificing reliability, as long as the production-specific details covered here — network exposure, restart policies, and backup timing — are handled deliberately rather than left to defaults. Consult the official Docker Compose documentation for the full specification as your setup grows more complex.

    Comments

    Leave a Reply

    Your email address will not be published. Required fields are marked *