Validate Docker Compose Before You Deploy

Validate Docker Compose Before You Deploy

Use docker compose config to catch broken variables, invalid YAML, and unsafe overrides before your self-hosted stack goes down.

๐Ÿ’ก Disclosure: This article contains affiliate links. If you make a purchase through these links, we may earn a small commission at no extra cost to you. This helps support the site and keeps the content free.

A Compose file can look perfectly reasonable and still take down a working service. One missing environment variable, one bad indentation level, or one override file loaded in the wrong order is enough. I have made all three mistakes, usually when I was trying to make a five-minute change late at night.

My rule now is blunt: never run docker compose up -d on an edited stack before running docker compose config. It takes seconds and catches the boring failures before they become an outage.

What docker compose config actually checks

Run this from the directory containing your Compose file:

docker compose config --quiet

No output and an exit code of zero means Docker successfully parsed and validated the resolved Compose configuration. It checks YAML syntax, Compose structure, merged override files, and variable interpolation.

That is more useful than staring at a diff and hoping. It will not prove that an image exists or that your app can talk to Postgres, but it stops a surprising number of bad deployments at the door.

If your project uses a non-default filename, be explicit:

docker compose -f compose.yml -f compose.production.yml config --quiet

The order matters. Later files override earlier ones, so validate the exact command you use to deploy.

Catch missing variables before they turn into empty passwords

This is the failure I see most often in small self-hosted stacks. A .env file is missing, a variable name was changed, and Docker substitutes an empty value or emits a warning that gets ignored in a noisy terminal.

Start with a Compose file that refuses to continue when a secret is absent:

services:
  app:
    image: ghcr.io/example/app:1.4.0
    environment:
      DATABASE_URL: postgresql://app:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}@db/app

The :? form makes the requirement explicit. Validation now fails with a useful message if POSTGRES_PASSWORD is not set:

docker compose config --quiet
# error while interpolating services.app.environment.DATABASE_URL:
# required variable POSTGRES_PASSWORD is missing a value: set POSTGRES_PASSWORD

For values that are genuinely optional, use a default instead:

LOG_LEVEL: ${LOG_LEVEL:-info}

Do not give passwords or tokens a convenient default. An app silently booting with changeme is worse than an app refusing to boot.

Inspect the resolved configuration, carefully

When validation fails, remove --quiet:

docker compose config

Docker prints the final merged YAML. This makes duplicate ports, unexpected image tags, and a production stack accidentally inheriting development settings painfully obvious.

There is one security catch. The rendered output can contain interpolated secrets. Do not paste it into an issue, a CI log, or a chat channel. I use it locally, inspect only the relevant service, then clear my terminal scrollback if I had to display real credentials.

For a narrower view, ask Compose for one field:

docker compose config --images
docker compose config --services
docker compose config --volumes

--images is especially good before an update. It answers the question that matters: which exact images is this command about to start?

Put the check in a tiny deploy script

You do not need a CI platform to make this habitual. A small script next to the stack is enough:

#!/usr/bin/env bash
set -euo pipefail

cd "$(dirname "$0")"
docker compose config --quiet
docker compose pull
docker compose up -d --remove-orphans
docker compose ps

Save it as deploy.sh, make it executable, and use it instead of typing the deployment command from memory:

chmod +x deploy.sh
./deploy.sh

set -e means a failed validation stops the script before the pull or restart. That ordering is deliberate. There is no prize for downloading a new image when the configuration that should run it is broken.

The checks this does not replace

Compose validation is a guardrail, not a test suite. It cannot tell you whether your reverse proxy route is correct, whether a mounted directory has the right ownership, or whether a database migration will succeed.

After a meaningful change, I still run these:

docker compose ps
docker compose logs --tail=100 app
curl --fail --silent --show-error https://app.example.com/health

Give each public app a health endpoint if it can have one. A container shown as Up only proves that a process is alive. It does not prove that the service your users need is working.

The boring habit worth keeping

The real value of docker compose config --quiet is not that it is clever. It is that it is cheap enough to run every time.

Add it before every Compose deployment, use required variable syntax for secrets, and inspect resolved YAML only on a trusted terminal. Your future self will still make a rushed edit. At least the server does not have to pay for it.

๐Ÿš€NordVPN

Managing a VPS from untrusted networks? Add a reliable VPN layer before opening your admin tools.

Get NordVPN โ†’

Affiliate link โ€” we may earn a commission at no extra cost to you.

Try this now: pick one Compose project, run docker compose config --quiet, then add it as the first line of its deployment script. It is the smallest reliability upgrade most homelabs are missing.

Stay in the loop ๐Ÿ“ฌ

Get self-hosting tutorials, tool reviews, and infrastructure tips delivered to your inbox. No spam, unsubscribe anytime.

Join 0 self-hosters. Free forever.