Docker Compose configs: sometimes the best features aren’t new
Docker Compose files all tend to look the same at first: services, maybe volumes and sometimes networks.
And then there’s configs.
It sits there quietly at the top level, documented, supported, and almost completely ignored (at least by me). For years.
I came across it while extending my Ansible role that builds and deploys Docker Compose stacks. The role already had pre- and post-tasks for copying configuration files to the host, keeping paths in sync, and cleaning up afterwards. Since I usually manage my remote server systems from my client and rarely work directly on the target host, this extra orchestration added even more complexity. That was the moment I realized that Docker Compose might have already solved the problem for me.
As it turns out, that's the case.

Configuration files don't need to exist on the host
A very common Docker Compose pattern looks like this:
services:
nginx:
image: nginx
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
This works. It always has.
But it comes with a few annoyances:
- The config file must exist on the host
- You have to copy or sync it there (scp, rsync, Ansible, Git checkout, pick your poison)
- Reproducibility takes a quiet hit
This gets worse once the target host is not your machine:
a VPS, a server, a Raspberry Pi under a desk somewhere, or anything provisioned automatically.
For a long time, my solution was “just manage it with Ansible”. Which works, but feels like bringing a forklift to move a chair.
The overlooked solution: configs
Docker Compose has a top-level element called configs.
It lets you define configuration files inside your Compose setup and mount them into containers, without bind mounts.
There are three main ways to define a config:
- Inline content (no file, directly in Compose)
- From a file (existing file on disk)
- From an environment variable (simple and small configuration)
Let’s see them in practice.
1. Inline content
This approach made me realize how much unnecessary complexity I had added with Ansible. However, this option would fit most of my needs.
configs:
nginx_inline_config:
content: |
server {
listen 80;
server_name localhost;
location / {
return 200 'Hello from inline config!';
}
}
services:
nginx:
image: nginx
ports:
- "8080:80"
configs:
- source: nginx_inline_config
target: /etc/nginx/nginx.conf
No extra file. No copy steps. One Compose file fully declares the configuration. As you might have guessed, this option works well in most cases, but it becomes confusing when the content is more extensive. In this case, the next one fits well.
2. Config from file
The difference here is subtle to my Ansible solution: the content still comes from a file, but Compose treats it as a managed config rather than a volume bind mount. This is good for reusability, for example, and provides a certain level of clear structure.
configs:
nginx_file_config:
file: ./nginx.conf
services:
nginx:
image: nginx
ports:
- "8080:80"
configs:
- source: nginx_file_config
target: /etc/nginx/nginx.conf
3. Config from environment variable
Sometimes configuration is small or dynamic, and you might want to pull it from an environment variable. Compose supports this too:
export REDIS_CONFIG="appendonly yes"configs:
redis_env_config:
environment: REDIS_CONFIG
services:
redis:
image: redis
configs:
- source: redis_env_config
target: /usr/local/etc/redis/redis.conf
command: ["redis-server", "/usr/local/etc/redis/redis.conf"](4.) External configs
Another way to use configs is via "external." In short, this allows you to reuse existing configurations, such as with networks.
configs:
redis_env_config_external:
name: redis_env_config
external: true
services:
redis:
image: redis
configs:
- source: redis_env_config_external
target: /usr/local/etc/redis/redis.conf
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]The takeaway
A few things to be aware of:
- Configs are mounted read-only. If your application insists on rewriting its config at startup, this won’t work.
- Updating a config requires recreating the container. Which is usually fine, but worth knowing.
Once I realized I could define configs inline (or pull them from files or environment variables), all those pre- and post-copy steps in my Ansible roles suddenly seemed unnecessary. The Compose file became a complete declaration of the service: image, ports, and configuration, all in one place.
For new users, it’s a subtle but powerful feature. For experienced Dockerists, it’s one of those “how did I miss this for so long?” moments.
Sometimes the best features aren’t new.
