Docker Compose Tip #89: What cap_drop actually takes away from root

Run this from a container whose process is uid 0: services: hardened: image: alpine cap_drop: - ALL command: sh -c "touch /tmp/f && chown 2000:2000 /tmp/f && echo OK" docker compose run --rm hardened chown: /tmp/f: Operation not permitted Tip #29 listed which cap_add values pair with which workload. This is the part worth sitting with: cap_drop: ALL just took a privilege away from uid 0, not from some non-root user it was supposedly protecting against. ...

September 23, 2026 · 2 min · 416 words · Guillaume Lours

Docker Compose Tip #88: Reversing a Compose file with docker compose alpha generate

docker compose alpha generate mycontainer --name myapp Point that at a container that’s already running and it inspects it, then writes back the Compose file you’d otherwise have to write by hand. Tip #28 covered that translation manually; generate does it straight from what’s actually running. What it produces Run it against a container started with docker run -p 127.0.0.1:8080:80 nginx:alpine: services: mycontainer: scale: 1 image: nginx:alpine environment: PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin NGINX_VERSION: "1.31.5" labels: maintainer: "NGINX Docker Maintainers <docker-maint@nginx.com>" ports: - host_ip: "127.0.0.1" target: 80 published: "8080" protocol: tcp It reads networks, volumes, and healthchecks the same way, and --format json swaps the output format if you’d rather feed it into other tooling. Note the environment and labels above: they include everything baked into the image, not just what you passed on the command line. Useful for capturing what a container actually runs with, but expect to trim the output before committing it. ...

September 21, 2026 · 3 min · 440 words · Guillaume Lours

Docker Compose Tip #87: The depends_on options tip #3 didn't cover

Tip #3 stopped at condition: service_healthy. The long-form depends_on syntax has three more fields, each solving a different problem: services: api: build: . depends_on: migrate: condition: service_completed_successfully cache: condition: service_started restart: true metrics: condition: service_started required: false service_completed_successfully: wait for a one-shot to finish service_healthy waits for a long-running service to become ready. service_completed_successfully waits for a service to exit, and only starts the dependent if it exited zero: ...

September 18, 2026 · 2 min · 379 words · Guillaume Lours

Docker Compose Tip #86: Three sync bugs that made watch unpredictable

Tip #11 covered sync and rebuild. One watch attribute it didn’t cover: initial_sync, which re-syncs a path into containers that already exist, for when you reattach watch to a stack that’s still running. services: web: build: . develop: watch: - path: ./src target: /app/src action: sync initial_sync: true Three separate bugs in that sync path, and one in sync generally, got fixed in 5.5.1. ...

September 16, 2026 · 3 min · 508 words · Guillaume Lours

Docker Compose Tip #84: Lifecycle hooks now show you why they failed

A failing lifecycle hook, post_start, pre_stop, or the new pre_start, used to report only its exit code: db hook exited with status 1 What the hook actually printed depended on how it ran. Under a plain docker compose up, the output streamed live to the terminal, mixed into the rest of up’s output, but was never carried into the error message itself. Under restart, run, or any tool driving Compose as a library, there was no listener attached at all, so Compose didn’t even request stdout or stderr from the daemon, and there was nothing to read afterward, live or otherwise. Since Compose 5.5.1, both cases keep a tail of the output and fold it straight into the error on failure. ...

September 11, 2026 · 2 min · 371 words · Guillaume Lours