[{"data":1,"prerenderedAt":182},["ShallowReactive",2],{"seo-verification":3,"blog-depends-on-is-not-enough-postgresql-healthcheck-in-compose-ma-en":6,"promo-bar-likely":181},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-depends-on-is-not-enough-postgresql-healthcheck-in-compose-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":121,"ctaBody":122,"ctaButton":123,"ctaUrl":124,"relatedPosts":125},282,"depends-on-is-not-enough-postgresql-healthcheck-in-compose",{"fr":12,"en":10,"ar":13,"es":14},"docker-compose-depends-on-healthcheck","depends-on-لا-يكفي-healthcheck-لـ-postgresql-في-compose","docker-compose-healthcheck-postgresql","depends_on is not enough: PostgreSQL healthcheck in Compose","Why `depends_on` alone does not guarantee PostgreSQL is ready, and how to configure a reliable healthcheck with `service_healthy` to avoid race conditions.",8,0,false,"2026-08-19T00:00:00+00:00","2026-09-07T11:26:10+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-depends-on-healthcheck-poster.svg","Your Docker stack starts, the database moves to `running` state — and your application crashes immediately with `connection refused` or `FATAL: role does not exist`. The cause is almost always the same: `depends_on` by default waits for the container to start, not for the service to be operational. This guide shows you how to configure a reliable PostgreSQL healthcheck in `docker-compose.yml` to eliminate this race condition once and for all.",[34,38,48,51,79,111,115,118],{"type":35,"title":36,"body":37},"h2","Why `depends_on` by default fails","By default, `depends_on` uses the `service_started` condition. This means Docker only waits for the target container to be **launched** — in other words, for its main process to have started. This says nothing about the internal state of the service.\n\nPostgreSQL, like most databases, goes through several phases during initialization: the official image runs bootstrap scripts, creates roles, initializes extensions and positions the cluster before it starts accepting connections. This sequence can take anywhere from a few seconds to more than thirty seconds on a VPS with a warm disk, an unprepared volume or a large set of extensions.\n\nDuring this time, your application — which correctly respects the `depends_on` directive — is already trying to connect, and receives a hard refusal.",{"type":39,"title":40,"items":41},"ul","The practical consequences of a startup race condition",[42,43,44,45,46,47],"**`connection refused`** — PostgreSQL's TCP socket is not yet open, the application fails on the first PDO or SQLAlchemy call.","**`FATAL: role does not exist`** — PostgreSQL is listening, but the initialization scripts (`docker-entrypoint-initdb.d`) have not yet created the role or database.","**`FATAL: the database system is starting up`** — the cluster is recovering from a clean shutdown; connections are temporarily refused.","**Silent crash loop** — Docker `restart: unless-stopped` relaunches the application indefinitely, logs repeat, and the problem looks like an application error.","**False positives in CI** — integration tests fail intermittently depending on the runner startup speed.","**Cascading dependencies** — an API that depends on an app that depends on the database inherits the same problem if the `depends_on` chain is not uniformly correct.",{"type":35,"title":49,"body":50},"Prerequisites: Docker Compose v2 and the official plugin","The `service_healthy` condition is **not** available in Docker Compose v1 (the Python `docker-compose` binary, now obsolete). It is supported since **Docker Compose v2**, distributed as a Go plugin under the `docker compose` command (no hyphen).\n\nTo check your version:\n\n```bash\ndocker compose version\n```\n\nThe output must show `Docker Compose version v2.x.x` or higher. On Debian 12 and Ubuntu 22.04+, the plugin is available from the official Docker repositories. If you still have `docker-compose` (v1), migrate: the project is archived and no longer receives security fixes.\n\nNo additional dependency is required for PostgreSQL: `pg_isready` is a native tool of the official `postgres` image, present in all tags for years.",{"type":52,"title":53,"steps":54},"steps","Configure a reliable PostgreSQL healthcheck, step by step",[55,58,61,64,67,70,73,76],{"title":56,"body":57},"Understand service_started vs service_healthy","`depends_on` accepts three conditions:\n\n- `service_started` (default) — waits for the container to simply be started.\n- `service_healthy` — waits for the container's healthcheck to return `healthy`.\n- `service_completed_successfully` — for short-lived containers (jobs, migrations).\n\nFor any database, `service_healthy` is the only condition that guarantees the service accepts connections.",{"title":59,"body":60},"Write the PostgreSQL healthcheck in the `db` service","Add the `healthcheck` block directly in the `db` service definition:\n\n```yaml\nservices:\n  db:\n    image: postgres:16\n    environment:\n      POSTGRES_USER: app\n      POSTGRES_PASSWORD: secret\n      POSTGRES_DB: appdb\n    healthcheck:\n      test: [\"CMD\", \"pg_isready\", \"-U\", \"app\", \"-d\", \"appdb\"]\n      interval: 5s\n      timeout: 5s\n      retries: 5\n      start_period: 30s\n```\n\nThe `test` field receives a list: the first element is `CMD` (Docker executes the command directly), followed by the arguments. `pg_isready` returns `0` if PostgreSQL is ready to accept connections for the given user and database, and a non-zero code otherwise — which Docker interprets as `healthy` or `unhealthy`.",{"title":62,"body":63},"Understand the role of `start_period`","`start_period` is the **grace window** given to the container to initialize before healthcheck failures start counting toward `retries`. During this window, Docker does run the healthcheck, but a failure does not increment the counter.\n\nWithout `start_period`, a PostgreSQL that takes 15 seconds to initialize would fail its first 5 checks (`interval: 5s` × 5 attempts = 25 seconds) and go `unhealthy` before it was even operational.\n\nThe recommended value is **30 seconds** for a standard PostgreSQL: long enough to absorb slow initializations (first start with empty volume, heavy extensions) without unnecessarily delaying startup under normal conditions. `interval` and `start_period` are distinct: `interval` paces checks under normal operation, `start_period` protects the bootstrap phase.",{"title":65,"body":66},"Write the `depends_on` with `condition: service_healthy`","In each service that depends on the database, replace the short form of `depends_on` with the long form with condition:\n\n```yaml\nservices:\n  app:\n    image: myapp:latest\n    depends_on:\n      db:\n        condition: service_healthy\n    environment:\n      DATABASE_URL: postgresql:\u002F\u002Fapp:secret@db:5432\u002Fappdb\n```\n\nWith this configuration, Docker waits for the `db` service healthcheck to return `healthy` before starting `app`. If `db` goes `unhealthy` after `retries` failures, `app` does not start.",{"title":68,"body":69},"Test with `docker compose up`","Launch the stack and observe the sequencing:\n\n```bash\ndocker compose up\n```\n\nYou will see in the logs lines such as:\n\n```\ndb  | database system is ready to accept connections\napp | Waiting for db to be healthy...\napp | Starting application server\n```\n\nTo check the healthcheck state at any time:\n\n```bash\ndocker inspect \u003Cdb_container_name> | grep -A 5 '\"Health\"'\n```\n\nThe output shows `Status: healthy`, `starting` or `unhealthy`, and lists the last check outputs.",{"title":71,"body":72},"Redis use case: adapted healthcheck","Redis does not have `redis-isready`, but its equivalent is `redis-cli ping`:\n\n```yaml\n  redis:\n    image: redis:7-alpine\n    healthcheck:\n      test: [\"CMD\", \"redis-cli\", \"ping\"]\n      interval: 5s\n      timeout: 3s\n      retries: 5\n      start_period: 10s\n```\n\n`redis-cli ping` returns `PONG` and exit code `0` if Redis accepts connections. Since Redis starts faster than PostgreSQL, `start_period: 10s` is generally sufficient.",{"title":74,"body":75},"MySQL \u002F MariaDB use case: `mysqladmin ping`","For MySQL or MariaDB, use `mysqladmin ping`:\n\n```yaml\n  mysql:\n    image: mariadb:11\n    environment:\n      MYSQL_ROOT_PASSWORD: secret\n      MYSQL_DATABASE: appdb\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"localhost\", \"-u\", \"root\", \"-psecret\"]\n      interval: 5s\n      timeout: 5s\n      retries: 5\n      start_period: 30s\n```\n\nNote: the password is concatenated directly to `-p` with no space (`-psecret`), which is the expected `mysqladmin` behavior. This command appears in `docker inspect`, so prefer a Compose secret or environment variable if confidentiality is a constraint.",{"title":77,"body":78},"Troubleshooting: four common errors","**`pg_isready: command not found`** — you are not using the official `postgres` image (or a derived image that includes it). Check with `docker compose exec db which pg_isready`.\n\n**Healthcheck looping, never `healthy`** — the `test` never returns `0`. Test manually: `docker compose exec db pg_isready -U app -d appdb`. If the command fails, check the `POSTGRES_USER` and `POSTGRES_DB` environment variables.\n\n**`start_period` too short** — on a first start with an empty volume, PostgreSQL can take more than 30 seconds. Increase to `60s` or observe the logs: `database system was shut down at … LOG:  database system is ready to accept connections` indicates the actual delay.\n\n**Container permanently `unhealthy`** — after `retries` failures, Docker marks the container `unhealthy` but does not restart it (that is the role of `restart`). Check `docker inspect` to see the output of the last checks and identify the failing command.",{"type":80,"title":81,"headers":82,"rows":86},"comparison","Default behavior vs with healthcheck",[83,84,85],"Case","Default behavior (`service_started`)","With `service_healthy`",[87,91,95,99,103,107],[88,89,90],"First start, empty volume","App starts before the database is ready → crash loop","App waits for PostgreSQL to be initialized and accepting connections",[92,93,94],"Restart after clean shutdown","App may start during PostgreSQL recovery phase","App waits until recovery is complete",[96,97,98],"Slow database (extensions, heavy init)","Race condition depending on host speed","No race condition: healthcheck validates actual state",[100,101,102],"Cascading dependencies (app → worker → db)","Each component must handle reconnection retries on its own","The condition chain guarantees startup order",[104,105,106],"Integration tests in CI","Intermittent results depending on runner speed","Deterministic results",[108,109,110],"Redis or MySQL instead of PostgreSQL","Same problem, default `depends_on` makes no distinction","Same solution, check command adapted to each engine",{"type":112,"title":113,"body":114},"tip","`pg_isready` or `SELECT 1`: which to choose?","Two variants of PostgreSQL healthcheck are commonly seen in the wild:\n\n- `[\"CMD\", \"pg_isready\", \"-U\", \"postgres\"]`\n- `[\"CMD-SHELL\", \"psql -U postgres -c 'SELECT 1'\"]\"`\n\n`pg_isready` is **more reliable** for a simple reason: it only tests the server's ability to accept TCP connections, without opening a SQL session. It returns `0` as soon as the server is listening and accepting the handshake, which is exactly what an application needs to attempt its own connection.\n\n`SELECT 1` via `psql` opens a real SQL session and executes a query. It is a deeper test, but it can fail for reasons unrelated to server availability (connection quota reached, misconfigured `pg_hba.conf`). For a healthcheck, the minimal and direct test is preferable.",{"type":112,"title":116,"body":117},"Adapting the healthcheck for Redis and MySQL","For Redis, replace `pg_isready` with `CMD redis-cli PING` — it returns `PONG` as soon as the server accepts connections. For MySQL or MariaDB, use `CMD mysqladmin ping -h localhost -u root --password=$$MYSQL_ROOT_PASSWORD`: raise `start_period` to 60s since MySQL initialization takes longer than PostgreSQL. The `condition: service_healthy` pattern is identical regardless of the target service.",{"type":35,"title":119,"body":120},"Next steps","The `service_healthy` healthcheck is one of the robustness settings to enable in production. Several other points deserve the same attention before a sustainable deployment: restart policy `restart: unless-stopped`, resource limits `deploy.resources.limits`, and log rotation `logging.options`. Find the complete checklist in **Docker Compose in Production: 10-Point Checklist**.\n\nIf your stack grows — multiple services, multiple hosts — a reverse proxy like Caddy or Traefik is essential for managing HTTPS routing. The **Caddy, Traefik or Nginx Proxy Manager** guide details the selection criteria by profile.\n\nTo automate the deployment of your entire infrastructure (VPS, Docker, configuration) in a reproducible way, **Ansible for automating your VPS servers** will guide you step by step.","Host your Docker stack on a dedicated VPS","A ServOrbit VPS gives you root access, a dedicated IPv4 and the resources needed to run your Docker Compose stacks in production. Deploy in minutes.","Deploy my stack on VPS","\u002Fvps-cloud",[126,143,163],{"id":127,"slug":128,"slugs":129,"title":133,"excerpt":134,"readTime":135,"views":136,"isPinned":19,"noindex":19,"publishedAt":137,"updatedAt":138,"category":139,"categories":140,"featuredImage":29,"bgImage":30,"posterImage":142,"relatedSolution":29},229,"docker-compose-in-production-10-point-checklist",{"fr":130,"en":128,"ar":131,"es":132},"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,"2026-08-06T00:00:00+00:00","2026-10-23T13:27:09+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":25},[141],{"id":23,"name":24,"slug":25,"color":26,"icon":25},"\u002Fblog\u002Fcovers\u002Fdocker-compose-production-checklist-poster.svg",{"id":144,"slug":145,"slugs":146,"title":150,"excerpt":151,"readTime":152,"views":153,"isPinned":19,"noindex":19,"publishedAt":137,"updatedAt":154,"category":155,"categories":160,"featuredImage":29,"bgImage":30,"posterImage":162,"relatedSolution":29},227,"caddy-traefik-or-nginx-proxy-manager-which-to-choose",{"fr":147,"en":145,"ar":148,"es":149},"choisir-reverse-proxy-vps-caddy-traefik-nginx","caddy-أم-traefik-أم-nginx-proxy-manager-أيهما-تختار","elegir-reverse-proxy-vps-caddy-traefik-nginx","Caddy, Traefik or Nginx Proxy Manager: which to choose?","Caddy, Traefik or Nginx Proxy Manager for your VPS? A practical comparison to choose the right reverse proxy for your Docker stack.",10,6,"2026-11-08T01:13:15+00:00",{"id":156,"name":157,"slug":158,"color":159,"icon":158},5,"Comparison","comparatif","bg-info\u002F10 text-info",[161],{"id":156,"name":157,"slug":158,"color":159,"icon":158},"\u002Fblog\u002Fcovers\u002Fchoisir-reverse-proxy-vps-caddy-traefik-nginx-poster.svg",{"id":164,"slug":165,"slugs":166,"title":170,"excerpt":171,"readTime":152,"views":172,"isPinned":19,"noindex":19,"publishedAt":173,"updatedAt":21,"category":174,"categories":178,"featuredImage":29,"bgImage":30,"posterImage":180,"relatedSolution":29},236,"automating-vps-server-management-with-ansible",{"fr":167,"en":165,"ar":168,"es":169},"ansible-automatiser-serveurs-vps","أتمتة-إدارة-خوادم-vps-باستخدام-ansible","automatizar-servidores-vps-con-ansible","Automating VPS Server Management with Ansible","Learn how to automate VPS fleet management with Ansible: inventory, playbooks, roles and Vault for a reproducible, auditable infrastructure.",2,"2026-08-08T00:00:00+00:00",{"id":172,"name":175,"slug":176,"color":177,"icon":176},"Automation","automatisation","bg-brand-action\u002F10 text-brand-action",[179],{"id":172,"name":175,"slug":176,"color":177,"icon":176},"\u002Fblog\u002Fcovers\u002Fansible-automatiser-serveurs-vps-poster.svg",true,1794270385717]