[{"data":1,"prerenderedAt":231},["ShallowReactive",2],{"seo-verification":3,"blog-docker-compose-in-production-10-point-checklist-ma-en":6,"promo-bar-likely":230},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-docker-compose-in-production-10-point-checklist-ma-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"noindex":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":27,"featuredImage":29,"bgImage":30,"posterImage":31,"relatedSolution":29,"intro":32,"sections":33,"ctaTitle":171,"ctaBody":172,"ctaButton":173,"ctaUrl":174,"relatedPosts":175},229,"docker-compose-in-production-10-point-checklist",{"fr":12,"en":10,"ar":13,"es":14},"docker-compose-production-checklist","docker-compose-في-الإنتاج-قائمة-التحقق-من-10-نقاط","checklist-docker-compose-en-produccion","Docker Compose in Production: 10-Point Checklist","Docker Compose in production: 10 settings, health checks, secrets, rollback, troubleshooting and moving from docker-compose v1 to docker compose on Ubuntu 26.04.",12,1,false,"2026-08-06T00:00:00+00:00","2026-10-23T13:27:09+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},3,"Deployment","deploiement","bg-success\u002F10 text-success",[28],{"id":23,"name":24,"slug":25,"color":26,"icon":25},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fdocker-compose-production-checklist-poster.svg","A docker-compose.yml that runs on your laptop won't survive production as-is. Restart after reboot, resource limits, secrets, log rotation: these settings are missing by default and cause silent outages once the service is exposed. This checklist gathers the 10 parameters to verify before deploying a Compose stack on a VPS, then health checks, secrets, rollback and troubleshooting. It also covers the move from `docker-compose` to `docker compose`, which upgrading to Ubuntu 26.04 forces you to settle.",[34,38,52,55,89,137,140,150,153,156,159,162,165,168],{"type":35,"title":36,"body":37},"h2","Why a checklist before production","Docker Compose was designed for development: its defaults favor simplicity over robustness. A container will not restart after a host reboot, its logs grow without limit, it can consume all the machine's RAM, and it listens on every network interface. In development, none of these behaviors cause problems because you relaunch the stack by hand several times a day. In production, these missing settings turn into incidents: full disk at 3 a.m., database lost after a `docker compose down`, service unreachable after a power outage. The good news: hardening a Compose stack requires no rewrite, only a dozen targeted additions. Run through each point before going live, and your service will handle load and reboots without constant monitoring.",{"type":39,"title":40,"items":41},"ul","The 10 points at a glance",[42,43,44,45,46,47,48,49,50,51],"**restart: unless-stopped** — the container restarts after a host reboot or crash","**healthcheck** — Docker detects a stuck container and enables rolling restarts","**CPU\u002Fmemory limits** — a runaway service can no longer starve its neighbors","**secrets via .env or Docker secrets** — never a plaintext password in the Compose file","**named volumes** — data survives a `docker compose down`","**log rotation** — `max-size` and `max-file` prevent disk saturation","**network isolation** — separate `frontend` and `backend`, don't expose everything on the default bridge","**port binding** — `127.0.0.1:PORT` behind a reverse proxy, not `0.0.0.0`","**pinned image tags** — a version or digest, never `latest`","**depends_on with condition** — `service_healthy` prevents startup races",{"type":35,"title":53,"body":54},"Prerequisites","You need a VPS with Docker Engine and the Compose v2 plugin. The command is `docker compose`, without a hyphen: check it with `docker compose version`, because the `deploy.resources` syntax outside Swarm requires Compose v2 (the old `docker-compose` is covered below). Check the engine too with `docker version`. Docker Engine 29.7.2 fixes \u003Ca href=\"https:\u002F\u002Fwww.imperva.com\u002Fblog\u002Fcopyescape-docker-vulnerability-cve-2026-17106\u002F\">CVE-2026-17106\u003C\u002Fa> (CopyEscape), a race condition in `docker cp` disclosed on 10 August 2026: an untrusted container can use it to overwrite a file on the host, including the `runc` binary. On workstations, the fix shipped in Docker Desktop 4.86.0. Until you update, do not run `docker cp` from a container whose content you do not control. Keep the project in a dedicated folder (`\u002Fopt\u002Fmyapp`), with a `.env` set to `chmod 600`. Plan for an upstream reverse proxy — Traefik, Caddy or Nginx — because port binding assumes public traffic never reaches your containers, and keep the Compose file under version control.",{"type":56,"title":57,"steps":58},"steps","The 10 steps in detail",[59,62,65,68,71,74,77,80,83,86],{"title":60,"body":61},"1. Restart policy","Add `restart: unless-stopped` to every service. The container restarts after a crash or host reboot, but stays stopped if you intentionally stopped it. Avoid `restart: always` which would relaunch even a container you wanted to leave stopped, and prefer `unless-stopped` for application services and `on-failure` for one-off tasks that should not loop indefinitely.",{"title":63,"body":64},"2. Healthcheck","Declare a `healthcheck:` block with a `test` (e.g. `curl -f http:\u002F\u002Flocalhost:8080\u002Fhealth || exit 1`), an `interval` (check frequency, e.g. `30s`), a `timeout` (failure threshold, e.g. `10s`) and `retries` (consecutive failures before switching to `unhealthy`, e.g. `3`). Also add `start_period: 40s` for services that take time to boot, to avoid marking them `unhealthy` during initialization. Docker then marks the container `healthy` or `unhealthy`, allowing other services to react to a stall via `depends_on: condition: service_healthy`.",{"title":66,"body":67},"3. Resource limits","Under `deploy.resources.limits`, set `cpus` and `memory` (e.g. `memory: 512M`). Without a limit, a leaking service can consume all RAM and cause the OOM killer to terminate others. Also add `reservations` to guarantee a minimum at startup. In Compose v2 outside Swarm, `limits` are applied as cgroup constraints; `reservations` are declarative and do not block startup, but serve as a planning reference.",{"title":69,"body":70},"4. Secrets outside the file","Never put a plaintext password in the YAML. Reference them via `env_file: .env` or `${VARIABLE}`, or use Docker's `secrets:` mechanism which mounts the secret as a file in `\u002Frun\u002Fsecrets\u002F` inside the container, out of reach of `docker inspect`. Add `.env` to your `.gitignore` and check with `git status` that this file is never tracked.",{"title":72,"body":73},"5. Named volumes","Declare your data in named volumes with an explicit driver rather than anonymous bind-mounts. A named volume survives `docker compose down`; only `down -v` removes it. Document each volume to know what you are backing up, and associate it with a tested backup strategy: a backup without a restore test is unverified data.",{"title":75,"body":76},"6. Log rotation","Add a `logging:` block with `driver: json-file` and options `max-size: \"10m\"` and `max-file: \"3\"`. Without this, a chatty container's logs fill the disk until failure. Apply it to every service. For high-volume stacks, consider the `local` driver (automatic compression) or an external driver like `loki` if you have a centralized logging infrastructure.",{"title":78,"body":79},"7. Network isolation","Create named networks — a `frontend` for exposed services, a `backend` for the database — and attach each service only to the networks it needs. Your database should only be reachable by the application, never from the shared default bridge. Add `internal: true` to the `backend` network to prevent any access from the host or the internet, even in the event of a port configuration error.",{"title":81,"body":82},"8. Port binding","Behind a reverse proxy, publish on `127.0.0.1:8080:8080` rather than `8080:8080` (which is equivalent to `0.0.0.0`). Otherwise the port remains accessible from the internet despite the proxy, bypassing your TLS and authentication rules. Note: Docker directly modifies iptables rules and can bypass UFW — binding to `127.0.0.1` is the only reliable guarantee on the Compose side.",{"title":84,"body":85},"9. Pinned image tags","Replace `image: postgres:latest` with a specific version (`postgres:16.3`) or, better, a digest (`postgres@sha256:...`). `latest` changes without warning and makes your deployments non-reproducible. Combine with `pull_policy: missing` for predictable behavior: Compose only pulls a new image if it is absent locally, avoiding unexpected updates on `docker compose up`.",{"title":87,"body":88},"10. Start order","Use `depends_on` with `condition: service_healthy` so a service waits not just for its dependency to start but to be genuinely available. This requires a healthcheck on the dependency (step 2) and eliminates startup races. For services that do not support a native healthcheck, you can use `condition: service_started` combined with a wait script (`wait-for-it.sh` or `dockerize`).",{"type":90,"title":91,"headers":92,"rows":96},"comparison","Dev defaults vs production settings",[93,94,95],"Parameter","Dev default","Recommended in prod",[97,101,105,109,113,117,121,125,129,133],[98,99,100],"restart","no","unless-stopped",[102,103,104],"healthcheck","absent","defined with interval and retries",[106,107,108],"memory","unconstrained","limit set (e.g. 512M)",[110,111,112],"secrets","plaintext possible",".env or Docker secrets",[114,115,116],"volumes","anonymous","named with driver",[118,119,120],"logs","no limit","max-size + max-file",[122,123,124],"network","default bridge","frontend \u002F backend separated",[126,127,128],"ports","0.0.0.0","127.0.0.1 behind proxy",[130,131,132],"image","latest","version or pinned digest",[134,135,136],"depends_on","start only","condition: service_healthy",{"type":35,"title":138,"body":139},"docker-compose or docker compose: the Ubuntu 26.04 break","Two tools carry almost the same name. `docker-compose`, with a hyphen, is Compose v1, written in Python: its repository declares it end of life since the general availability of Compose v2, announced by Docker on 26 April 2022. `docker compose`, with a space, is Compose v2, written in Go and plugged into the Docker CLI as a plugin. Ubuntu 26.04 completes the transition: the `docker-compose` package has been out of the archive since Ubuntu 25.10, and the `docker-compose-v2` package installs only the plugin, in `\u002Fusr\u002Flibexec\u002Fdocker\u002Fcli-plugins\u002F`. After the upgrade, any script that still calls the old name stops on `docker-compose: command not found`. On the CI side, GitHub took Compose v1 out of its hosted runners on 9 July 2024, and moves `ubuntu-latest` to Ubuntu 26.04 between 19 October and 19 November 2026: a workflow that installed `docker-compose` by hand needs a second look. Two behaviour differences are documented. Container names use a hyphen instead of an underscore (`myapp-web-1` versus `myapp_web_1`), unless you pass `--compatibility`. And the `version:` key is ignored, with the warning “the attribute `version` is obsolete, it will be ignored”.",{"type":39,"title":141,"items":142},"Migrate your scripts and add a shim",[143,144,145,146,147,148,149],"**Inventory** — `grep -rnE 'docker-compose( |$)' \u002Fopt \u002Fetc\u002Fcron* \u002Fetc\u002Fsystemd ~\u002F.bashrc` lists the calls to fix.","**Scripts and Makefile** — replace the hyphen with a space (`docker compose up -d`); the common subcommands stay the same.","**cron and systemd** — call `\u002Fusr\u002Fbin\u002Fdocker compose` with an absolute path, because cron runs with a reduced `PATH`.","**GitHub Actions** — test your workflows with `runs-on: ubuntu-26.04` before the switch, or pin `ubuntu-24.04` while you fix them, as GitHub recommends.","**Temporary shim** — a two-line executable file `\u002Fusr\u002Flocal\u002Fbin\u002Fdocker-compose`, `#!\u002Fbin\u002Fsh` then `exec docker compose \"$@\"`, catches forgotten calls. Docker also maintains `compose-switch`, which translates v1 syntax to v2.","**Container names** — replace hard-coded `docker exec myapp_web_1` calls with `docker compose exec web`, which does not depend on the separator.","**Verification** — `docker compose version` answers, `docker compose config` passes without a warning, `command -v docker-compose` points only to the shim.",{"type":35,"title":151,"body":152},"Advanced health checks: monitoring real service state","A well-configured health check goes beyond a simple `curl`: it measures the functional state of the service, not just network availability. For an HTTP API, test a lightweight business endpoint (`\u002Fhealth` or `\u002Fready`) that returns 200 only if the database connection is established. For PostgreSQL, use `pg_isready -U postgres` rather than curl. For Redis, `redis-cli ping` is sufficient. Check the state of your containers with `docker compose ps`: the `STATUS` column shows `healthy`, `unhealthy` or `starting`. A container stuck in `starting` beyond `start_period` signals an initialization problem — consult `docker compose logs service` to find the cause. The `start_period` parameter is distinct from `interval`: it defines a grace window during which failures do not count toward `retries`, which is essential for services that need time to populate data or run schema migrations.",{"type":35,"title":154,"body":155},"Docker secrets without Swarm: mounting files in \u002Frun\u002Fsecrets\u002F","Outside Swarm, Compose accepts file-based secrets. Declare the secret in the root-level `secrets:` section, pointing to a local file (`file: .\u002Fsecrets\u002Fdb_password.txt`), then reference it in the service with `secrets: [db_password]`. Docker mounts that file read-only in `\u002Frun\u002Fsecrets\u002Fdb_password`: the application reads it as an ordinary file, and its value appears neither in the environment variables shown by `docker inspect` nor in the logs. To rotate, version the secret: create `db_password_v2` next to `db_password_v1`, make the service read `v2`, redeploy it alone with `docker compose up -d --no-deps service`, then delete `v1` once the deployment is validated. The interruption is limited to recreating that one container, and the old secret stays available if you need to roll back.",{"type":35,"title":157,"body":158},"Rollback: recovering from a failed deployment","Rolling back a Compose stack is prepared before the deployment, not after the incident. Keep the previous image tagged and available locally: add an explicit `myapp:stable` tag before each update. To roll back, put the old tag back in the Compose file (or in the `.env` if the version is a variable), then rerun `docker compose up -d`. Volumes are not touched by an image change, which makes an application rollback fast; a schema migration, however, needs its reverse migration ready before the deployment. For critical releases, split `docker-compose.yml` (stable version) and `docker-compose.override.yml` (candidate version): `docker compose -f docker-compose.yml up -d` ignores the override and returns to the stable state.",{"type":160,"body":161},"tip","Always test your hardened stack locally before pushing to production: run `docker compose config` to validate the syntax, then `docker compose up` and simulate a reboot with `docker compose restart`. Verify that containers come back `healthy` and that data persists after a `down` followed by an `up`.",{"type":35,"title":163,"body":164},"Troubleshooting: 6 common errors and their solutions","**`docker: unknown command: docker compose`**: the Compose plugin is not installed. On Ubuntu, install `docker-compose-v2` from the archive, or `docker-compose-plugin` if Docker comes from Docker's official repository, then check with `docker compose version`. **`network not found`**: after a `docker compose down`, named networks are removed. If you restart and an external container tries to join that network, you get this error. Solution: declare shared networks as `external: true` or synchronize the restart of dependent stacks. **`port is already in use`**: another process (host nginx, an orphaned stack) occupies the port. Identify it with `ss -tlnp | grep :PORT` then stop it before restarting. **OOM (Out Of Memory)**: the kernel kills a container without warning — look for `OOMKilled: true` in `docker inspect container_id`. Raise the memory limit (`deploy.resources.limits.memory`) or identify the leak with `docker stats`. **`permission denied on volume`**: the container process runs with a UID that has no access to the bind-mounted directory. Check the UID with `docker compose exec service id`, then adjust the host folder permissions (`chown -R UID:GID \u002Fpath`) or add `user: \"UID:GID\"` to the service. **Container restarting in a loop**: `docker compose logs --tail=50 service` reveals the startup error. The most common causes are a missing environment variable, a missing config file or an unready dependency (solved by `depends_on` with `service_healthy`).",{"type":35,"title":166,"body":167},"Backing up volumes without corruption","Copying files from a running PostgreSQL or MySQL volume with `rsync` or `tar` almost always produces a corrupted backup: the engine writes continuously during the copy, and data pages are captured at different checkpoints. The rule is to **always run a SQL dump before the volume snapshot**: `pg_dump` or `mysqldump` produce a consistent state you can archive or transfer. To automate this approach on a Docker VPS, tools like \u003Ca href=\"https:\u002F\u002Fpistack.xyz\u002Fposts\u002F2026-05-11-self-hosted-docker-volume-backup-offen-nautical-loomchild-guide\u002F\">Restic and Offen Docker Backup\u003C\u002Fa> orchestrate database freeze, SQL dump, encrypted snapshot and upload to remote storage. Document each named volume (step 5 of the checklist) and associate it with a tested restore strategy: a backup without a restore test is unverified data.",{"type":35,"title":169,"body":170},"Conclusion","These ten settings turn a development Compose file into a stack that survives reboots, load spikes and unsupervised nights. Health checks, file-mounted secrets, a prepared rollback and a complete move to `docker compose` complete this foundation. None of it requires an additional tool: everything fits in the YAML and scripts you already have. Run through the checklist before each release, for example through a `docker compose config` review built into the deployment. You can then add a reverse proxy such as Traefik or Caddy, or an orchestration layer such as Coolify.","Deploy your Compose stack in production","A ServOrbit VPS gives you the root access, RAM and network control needed to run a hardened Docker Compose stack. Pick your configuration and deploy in minutes.","Deploy on ServOrbit VPS","\u002Fvps-cloud",[176,192,212],{"id":177,"slug":178,"slugs":179,"title":183,"excerpt":184,"readTime":185,"views":23,"isPinned":19,"noindex":19,"publishedAt":186,"updatedAt":187,"category":188,"categories":189,"featuredImage":29,"bgImage":30,"posterImage":191,"relatedSolution":29},38,"deploy-your-applications-with-traefik-on-a-vps",{"fr":180,"en":178,"ar":181,"es":182},"deployer-avec-traefik","انشر-تطبيقاتك-باستخدام-traefik-على-خادم-vps","desplegar-con-traefik","Deploy your applications with Traefik on a VPS","Deploy Traefik as a reverse proxy on your VPS: automatic routing of Docker containers, Let's Encrypt SSL certificates and load balancing.",11,"2026-05-13T00:00:00+00:00","2026-11-08T01:13:13+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[190],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Fdeployer-avec-traefik-poster.svg",{"id":193,"slug":194,"slugs":195,"title":199,"excerpt":200,"readTime":201,"views":202,"isPinned":19,"noindex":19,"publishedAt":203,"updatedAt":204,"category":205,"categories":206,"featuredImage":29,"bgImage":30,"posterImage":208,"relatedSolution":209},39,"deploy-your-applications-with-caddy-on-a-vps",{"fr":196,"en":194,"ar":197,"es":198},"deployer-avec-caddy","انشر-تطبيقاتك-باستخدام-caddy-على-خادم-vps","desplegar-con-caddy","Caddy as a reverse proxy on VPS: complete guide","Install and configure Caddy as a reverse proxy on your VPS: automatic Let's Encrypt HTTPS, multi-app setup, security hardening and common error troubleshooting.",8,4,"2026-05-12T00:00:00+00:00","2026-09-25T23:43:04+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[207],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Fdeployer-avec-caddy-poster.svg",{"categorySlug":210,"appSlug":211},"application-deployment-devops","caddy",{"id":213,"slug":214,"slugs":215,"title":219,"excerpt":220,"readTime":17,"views":221,"isPinned":19,"noindex":19,"publishedAt":222,"updatedAt":223,"category":224,"categories":225,"featuredImage":29,"bgImage":30,"posterImage":227,"relatedSolution":228},41,"deploy-your-applications-with-coolify-on-a-vps",{"fr":216,"en":214,"ar":217,"es":218},"deployer-avec-coolify","نشر-تطبيقاتك-باستخدام-coolify-على-vps","desplegar-aplicaciones-con-coolify-en-un-vps","Deploy Your Applications with Coolify on a VPS","Deploy Coolify on a VPS: installation, HTTPS configuration, Git connection, deploying your first application, databases, backups, and troubleshooting.",2,"2026-05-10T00:00:00+00:00","2026-10-31T01:54:10+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[226],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Fdeployer-avec-coolify-poster.svg",{"categorySlug":210,"appSlug":229},"coolify",true,1794270396002]