Your homelab’s docker-compose.yml starts small. Traefik, PostgreSQL,
maybe a Redis cache. Then you add Grafana for dashboards. Prometheus
and Node Exporter for metrics. Loki collects container logs. Adminer
makes database queries convenient. Portainer gives you a web UI for
containers. Watchtower auto-updates everything.
Suddenly you have twenty services in one file, and docker compose up
starts every single one. Your 8 GB Proxmox LXC hits swap before
you can disable the monitoring stack you only need for debugging.
The dev tools you forgot to stop eat RAM all night.
Docker Compose profiles fix this. They let you tag services into groups and start only the groups you need. Same compose file, zero duplication, full control over what runs when.
How Docker Compose Profiles Work
The profiles attribute on a service defines which profile names
activate it. A service with no profiles always starts with a plain
docker compose up. A service with at least one profile only starts
when that profile is explicitly requested.
|
|
Run docker compose up -d and only db starts. Run
docker compose --profile frontend up -d and both services start.
This small mechanism unlocks a clean organization pattern for homelabs running a dozen or more containers on a single Docker host.
Homelab Infrastructure Stack with Profiles
The most practical setup defines service tiers as profiles. Core infrastructure runs automatically. Monitoring, dev tools, and batch jobs start on demand.
Here is the complete structure I run on a Proxmox LXC at home:
|
|
What This Setup Achieves
| Profile | Services | Start Command | When to Use |
|---|---|---|---|
| (none) | Traefik, PostgreSQL, Redis | docker compose up -d |
Always on — boot with the host |
infra |
Prometheus, Grafana, Loki | --profile infra |
Daily monitoring |
monitoring |
Node Exporter, cAdvisor | --profile monitoring |
Debug or capacity planning |
dev |
Adminer, Portainer | --profile dev |
Active development sessions |
batch |
Watchtower | --profile batch |
Nightly updates |
Startup Commands for Day-to-Day Usage
Start the core stack at boot:
|
|
Add the infrastructure dashboard stack:
|
|
Fire up everything for a maintenance session:
|
|
The --profile flag accepts multiple values by repeating it. You
can combine any set of profiles depending on what you need right now.
Start every profiled and unprofiled service at once:
|
|
Stop all monitoring services without touching the core stack:
|
|
Dev vs Production Profiles
Profiles pair naturally with environment-specific overrides. A typical dev setup exposes database ports and includes query browsers that should never appear in production.
|
|
Now docker compose --profile dev up -d starts the core stack and
exposes database ports for external tools like DBeaver or psql.
Without the --profile dev flag, the databases run but remain
inaccessible from the Docker host — exactly what you want in
production.
This pattern also works for service variants. Use different images or tags per profile:
|
|
Splitting Large Compose Files with Includes
When a single compose file grows past 300 lines, split it by profile domain using Docker Compose includes (Compose v2.30+):
|
|
Each included file defines its own services with profiles:
|
|
Profiles work identically across includes — --profile infra starts
all services tagged with infra regardless of which file they
live in.
Best Practices for Docker Compose Profiles in Homelab
-
Default services stay unprofiled. If a service must run for the homelab to function (reverse proxy, auth, database), do not assign a profile. It starts automatically.
-
Name profiles by purpose, not by service. Use
monitoring,dev,batch,experimental— notgrafana,prometheus,adminer. This keeps the profile count manageable and the intent clear. -
Start everything at boot with
--profile '*'in your systemd service or init script. The host reboot starts all containers regardless of profile:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16# /etc/systemd/system/docker-homelab.service [Unit] Description=Docker Compose Homelab Requires=docker.service After=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/opt/homelab ExecStart=/usr/bin/docker compose --profile '*' up -d ExecStop=/usr/bin/docker compose --profile '*' down User=root [Install] WantedBy=multi-user.target -
Document the profile assignment in your README or as a comment at the top of each compose file. When you return to a project after six months, you want to know
--profile monitoringstarts which services. -
Use environment variables for profile-level configuration. Set
MONITORING_ENABLED=truein your.envand use it to conditionally include files or configure service parameters.
Troubleshooting Common Profile Issues
Container won’t start with docker compose up
Check that the service has a profile. Services with a profiles
list are excluded from the default up. Start them with
--profile <name>.
Service with depends_on fails because its dependency has a
profile
Compose does not automatically activate profiles for dependencies. If service A (profiled) depends on service B (profiled), you must pass both profiles:
|
|
Listing which services belong to each profile
|
|
Services without profiles appear in the default output. Add
--profile to see profiled services:
|
|
Check if a profile is active
|
|
Conclusion
Docker Compose profiles solve a real problem for growing homelabs. Instead of maintaining multiple compose files, environment-specific overrides, or commenting services in and out, you tag each service with a profile name and start only what you need.
The monitoring stack starts when you want to debug. Dev tools run only during development sessions. Batch jobs execute on a schedule. Core infrastructure stays up without interference.
One compose file, one directory, multiple service groups. Your RAM budget — and your future self — will thank you.