Like many, have become my go-to tool to create software that can be conveniently deployed to production with a limited amount of headache. However, many tasks, and sometimes whole services, pertain only to the development side of the workflow, and need to stay there.
Moreover, some tasks, such as time-consuming provisioning tasks, are only on-demand one-offs. They shouldn’t run at all most of the time, but they should slot into the dependency graph correctly when needed.
tl;dr: I realised that . Profiles are also useful to package actions and triggers to run on demand, so they are not started by default.
We can start with a simple setup where our long-running main service depends on an init service to perform preliminary steps. This can be setup with depends_on the compose.yaml.
services:
main:
image: debian:latest
command: "sh -c 'while : ; do echo main; sleep 10; done"
depends_on:
init:
condition: service_completed_successfully
init:
image: debian:latest
command: sh -c 'echo init; sleep 10'
Even when run ning the main container, we get the right dependency (and delay). So far so good (though up will show the output from all containers.
![Klick für Vollbild-Ansicht 🔍 Screenshot of a terminal. ``` [21:25:38] ~/docker-profiles$ docker compose run main 5s [+] 2/2t 2/22 ✔ Network docker-profiles_default Created 0.1s ✔ Container docker-profiles-init-1 Started 0.3s Container docker-profiles-init-1 Waiting Container docker-profiles-init-1 Exited Container docker-profiles-main-run-17209b1867a1 Creating Container docker-profiles-main-run-17209b1867a1 Created main main main ^C [21:26:50] ~/docker-profiles$ docker compose down 33s 130 ↵ [+] down 2/2 ✔ Container docker-profiles-init-1 Removed 0.0s ✔ Network docker-profiles_default Removed 0.1s [21:26:58] ~/docker-profiles$ docker compose up 1s WARN[0000] Found orphan containers ([docker-profiles-main-run-17209b1867a1]) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up. [+] up 3/3 ✔ Network docker-profiles_default Created 0.1s ✔ Container docker-profiles-init-1 Created 0.1s ✔ Container docker-profiles-main-1 Created 0.0s Attaching to init-1, main-1 init-1 | init Container docker-profiles-init-1 Waiting init-1 exited with code 0 Container docker-profiles-init-1 Exited main-1 | main main-1 | main Gracefully Stopping... press Ctrl+C again to force Container docker-profiles-main-1 Stopping main-1 | main Container docker-profiles-main-1 Stopped Container docker-profiles-init-1 Stopping Container docker-profiles-init-1 Stopped main-1 exited with code 137 [21:28:26] ~/docker-profiles$ ```](https://blog.narf.ssji.net/wp-content/uploads/sites/3/2026/05/image.png)
But what if we have another, much more time consuming, initialisation step?
services:
[...]
opt-init:
image: debian:latest
command: sh -c 'echo opt-init; sleep 100'
Perhaps we are lucky, and while it needs to run once, we don’t need it to run everytime (think: database setup).
.
services:
main:
[...]
opt-init:
condition: service_completed_successfully
required: false
And that’s really all there is to it: with the right profile, the optional dependency is started in the desired order, but its absence is otherwise transparently ignored. Both docker compose up and docker compose --profile opt work as desired.
![Klick für Vollbild-Ansicht 🔍 Screenshot of a terminal. ``` [21:40:31] ~/docker-profiles$ docker compose up 4s Attaching to init-1, main-1 init-1 | init Container docker-profiles-init-1 Waiting init-1 exited with code 0 Container docker-profiles-init-1 Exited main-1 | main main-1 exited with code 0 [21:40:35] ~/docker-profiles$ docker compose --profile opt up 3s Attaching to init-1, main-1, opt-init-1 opt-init-1 | opt-init init-1 | init Container docker-profiles-init-1 Waiting Container docker-profiles-opt-init-1 Waiting Container docker-profiles-opt-init-1 Exited opt-init-1 exited with code 0 init-1 exited with code 0 Container docker-profiles-init-1 Exited main-1 | main main-1 exited with code 0 ```](https://blog.narf.ssji.net/wp-content/uploads/sites/3/2026/05/image-2.png)
Profiles afford us another useful trick: on-demand tasks not started by default. This can be handy for maintenance tasks (data cleanup, garbage collection, …) or test scripts (running test workload, sending message, …). Those are handy during development, but would not be necessary, or take a different form, in other deployments.
services:
[...]
say-hello:
image: debian:latest
profiles:
- hello
command: echo hello
depends_on:
main:
condition: service_started
Conveniently, when explicitly running a service, it is not necessary to request a matching profile, keeping the command line lean: docker compose run say-hello.
![Klick für Vollbild-Ansicht 🔍 Screenshot of a terminal. ``` [21:47:53] ~/docker-profiles$ docker compose --profile opt down --remove-orphans 130 ↵ [+] down 5/5 ✔ Container docker-profiles-main-1 Removed 10.2s ✔ Container docker-profiles-init-1 Removed 0.0s ✔ Container docker-profiles-opt-init-1 Removed 0.0s ✔ Container docker-profiles-say-hello-run-c26e752b1edd Removed 0.0s ✔ Network docker-profiles_default Removed 0.1s [21:48:05] ~/docker-profiles$ docker compose run say-hello 11s [+] 3/3t 3/33 ✔ Network docker-profiles_default Created 0.1s ✔ Container docker-profiles-init-1 Exited 1.9s ✔ Container docker-profiles-main-1 Started 2.1s Container docker-profiles-say-hello-run-76efc04798b4 Creating Container docker-profiles-say-hello-run-76efc04798b4 Created hello ```](https://blog.narf.ssji.net/wp-content/uploads/sites/3/2026/05/image-3.png)
So here we are. Compose profiles allow us to control which services get started, and mark some as conditional. This, coupled with the ability to mark some depends_on rules as not required is a good way to seamlessly prevent heavy or otherwise time consuming services from starting when not needed, while retaining proper dependency ordering when enabled.
For completeness, the full, final, compose.yaml looks as follow.
services:
main:
image: debian:latest
command: "sh -c 'while : ; do echo main; sleep 10; done'"
depends_on:
init:
condition: service_completed_successfully
opt-init:
condition: service_completed_successfully
required: false
init:
image: debian:latest
command: sh -c 'echo init; sleep 10'
opt-init:
image: debian:latest
profiles:
- opt
command: sh -c 'echo opt-init; sleep 100'
say-hello:
image: debian:latest
profiles:
- hello
command: echo hello
depends_on:
main:
condition: service_started
The post .
SOCIAL SHARE CARD GENERATOR