
Docker Syntax Compose Resources
- 8 installs
- 9 repo stars
- Updated July 8, 2026
- openaec-foundation/docker-claude-skill-package
Helps with devops & ci/cd tasks.
About
docker-syntax-compose-resources is a Claude Code skill for devops & ci/cd. It helps solo builders move faster with AI-assisted development.
- docker-syntax-compose-resources
- DevOps & CI/CD
- AI-coding skill
Docker Syntax Compose Resources by the numbers
- 8 all-time installs (skills.sh)
- Ranked #1,044 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/docker-claude-skill-package --skill docker-syntax-compose-resourcesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 8 |
|---|---|
| repo stars | ★ 9 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/docker-claude-skill-package ↗ |
What it does
Helps with devops & ci/cd tasks.
Files
docker-syntax-compose-resources
Quick Reference
Top-Level Resource Elements
| Element | Purpose | Default Behavior |
|---|---|---|
networks | Define named networks for service communication | Compose creates implicit default network |
volumes | Define named volumes for persistent data | Created on docker compose up if missing |
configs | Define non-sensitive configuration data | Mounted at /<config-name> with mode 0444 |
secrets | Define sensitive data (passwords, certificates) | Mounted at /run/secrets/<secret-name> |
Network Driver Comparison
| Driver | Scope | Use Case | Multi-Host |
|---|---|---|---|
bridge | Single host | Default. Isolated network between containers | No |
host | Single host | Container shares host network stack directly | No |
overlay | Multi-host | Swarm service communication across nodes | Yes |
macvlan | Single host | Container gets own MAC address on physical network | No |
none | Single host | Completely disable networking | No |
Config vs Secret Comparison
| Attribute | Config | Secret |
|---|---|---|
| Purpose | Non-sensitive configuration | Sensitive credentials |
| Default mount path | /<config-name> | /run/secrets/<secret-name> |
| Default permissions | 0444 (world-readable) | 0444 (world-readable) |
| Source options | file, environment, content | file, environment |
| Customizable mount | Yes (target, uid, gid, mode) | Yes (target, uid, gid, mode) |
content inline | Yes (Compose 2.23.1+) | No |
Critical Warnings
NEVER use anonymous volumes for data that must persist -- anonymous volumes are recreated on docker compose down and all data is lost. ALWAYS define named volumes in the top-level volumes section.
NEVER omit the top-level declaration for a named volume, config, or secret -- referencing an undeclared resource in a service causes a Compose validation error. ALWAYS declare every resource at the top level.
NEVER set external: true on a resource without ensuring it exists before running docker compose up -- Compose does NOT create external resources and errors immediately if they are missing.
NEVER combine external: true with driver, driver_opts, file, content, or other creation attributes -- when external is set, only name is relevant alongside it. Compose rejects files with additional fields on external resources.
ALWAYS use reverse-DNS notation for resource labels (e.g., com.example.description) -- this prevents naming collisions with labels from other tools.
---
Decision Trees
Which Resource Type to Use
Need to store data persistently across container restarts?
├─ Yes → Use a VOLUME (top-level `volumes`)
│ ├─ Data owned by this Compose project? → Define normally
│ └─ Data shared across projects? → Use `external: true`
└─ No → Need to inject file-based configuration?
├─ Contains sensitive data (passwords, keys, certs)?
│ └─ Yes → Use a SECRET (top-level `secrets`)
└─ Non-sensitive configuration?
└─ Yes → Use a CONFIG (top-level `configs`)Which Network Driver to Use
Need containers to communicate?
├─ Single Docker host?
│ ├─ Standard container isolation → driver: bridge (default)
│ ├─ Container needs host network performance → driver: host
│ └─ Container needs own MAC on physical LAN → driver: macvlan
├─ Multiple Docker hosts (Swarm)?
│ └─ driver: overlay
└─ Container must have no network access?
└─ driver: noneExternal vs Managed Resources
Is the resource created outside this Compose project?
├─ Yes → external: true
│ ├─ Name matches Compose key? → Just set external: true
│ └─ Different name? → Add name: "actual-name"
└─ No → Let Compose manage creation and lifecycle
├─ Need custom driver? → Set driver + driver_opts
├─ Need custom subnet? → Set ipam.config
└─ Default behavior sufficient? → Declare with empty body---
Top-Level Networks
Basic Network Definition
networks:
frontend:
backend:
driver: bridgeAn empty declaration uses the default bridge driver.
Network with IPAM Configuration
networks:
app-net:
driver: bridge
ipam:
driver: default
config:
- subnet: 172.28.0.0/16
ip_range: 172.28.5.0/24
gateway: 172.28.5.254
aux_addresses:
host1: 172.28.1.5Network Attributes
networks:
internal-net:
internal: true # Externally isolated -- no internet access
attachable: true # Standalone containers can attach
enable_ipv6: true # Enable IPv6
labels:
com.example.project: "myapp"
name: "custom-net-name"
driver_opts:
com.docker.network.bridge.host_binding_ipv4: "127.0.0.1"External Network
networks:
shared:
external: true
name: "${NETWORK_ID}" # Variable interpolation supportedCustomizing the Default Network
networks:
default:
name: my-app-network
driver: bridge
driver_opts:
com.docker.network.bridge.host_binding_ipv4: "127.0.0.1"Service-Level Network Configuration
services:
app:
networks:
backend:
aliases:
- app-alias
- api
ipv4_address: 172.16.238.10
ipv6_address: 2001:3984:3989::10
priority: 1000---
Top-Level Volumes
Basic Volume Definition
volumes:
db-data:
cache:
driver: localVolume with NFS Driver
volumes:
nfs-data:
driver_opts:
type: "nfs"
o: "addr=10.40.0.199,nolock,soft,rw"
device: ":/docker/example"External Volume
volumes:
shared-data:
external: true
name: actual-volume-nameVolume with Labels
volumes:
db-data:
labels:
com.example.description: "Database volume"
com.example.department: "IT/Ops"
name: "${DATABASE_VOLUME}"---
Top-Level Configs and Secrets
Config Sources
configs:
from-file:
file: ./httpd.conf
from-env:
environment: "CONFIG_VALUE" # Compose 2.23.1+
from-inline:
content: | # Compose 2.23.1+
debug=${DEBUG}
app.name=${COMPOSE_PROJECT_NAME}
from-external:
external: true
name: "${HTTP_CONFIG_KEY}"Secret Sources
secrets:
from-file:
file: ./server.cert
from-env:
environment: "OAUTH_TOKEN"
from-external:
external: true
name: "${SECRET_KEY}"Mounting in Services
services:
web:
configs:
- from-file # Short: mounts at /<config-name>
- source: from-inline
target: /etc/app/config.properties
uid: "1000"
gid: "1000"
mode: 0440
secrets:
- from-file # Short: mounts at /run/secrets/<name>
- source: from-env
target: oauth-token
uid: "103"
gid: "103"
mode: 0440---
Network Isolation Pattern
services:
proxy:
image: nginx
networks:
- frontend
app:
image: myapp
networks:
- frontend
- backend
db:
image: postgres
networks:
- backend
networks:
frontend:
backend:
internal: true # No external access for database networkIn this pattern, proxy CANNOT reach db -- only app bridges both networks. Setting internal: true on backend prevents containers on that network from reaching the internet.
---
Complete Resource Example
services:
web:
image: nginx
configs:
- source: nginx-config
target: /etc/nginx/nginx.conf
mode: 0440
secrets:
- tls-cert
- source: tls-key
target: /etc/ssl/private/server.key
mode: 0400
volumes:
- static-files:/usr/share/nginx/html:ro
networks:
frontend:
aliases:
- webserver
app:
image: myapp
volumes:
- app-data:/data
networks:
- frontend
- backend
db:
image: postgres
volumes:
- db-data:/var/lib/postgresql/data
secrets:
- db-password
networks:
- backend
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
volumes:
db-data:
app-data:
static-files:
configs:
nginx-config:
file: ./nginx/nginx.conf
secrets:
tls-cert:
file: ./certs/server.crt
tls-key:
file: ./certs/server.key
db-password:
environment: POSTGRES_PASSWORD---
Reference Links
- references/networks.md -- All network options, drivers, and IPAM configuration
- references/volumes-configs-secrets.md -- Volume, config, and secret definitions and mounting
- references/anti-patterns.md -- Resource configuration mistakes and corrections
Official Sources
- https://docs.docker.com/compose/compose-file/06-networks/
- https://docs.docker.com/compose/compose-file/07-volumes/
- https://docs.docker.com/compose/compose-file/08-configs/
- https://docs.docker.com/compose/compose-file/09-secrets/
Anti-Patterns: Resource Configuration Mistakes
Network Anti-Patterns
Using flat networking (single default network for everything)
# WRONG -- all services on one network, no isolation
services:
proxy:
image: nginx
app:
image: myapp
db:
image: postgres
redis:
image: redis# CORRECT -- network segmentation isolates database tier
services:
proxy:
image: nginx
networks: [frontend]
app:
image: myapp
networks: [frontend, backend]
db:
image: postgres
networks: [backend]
redis:
image: redis
networks: [backend]
networks:
frontend:
backend:
internal: trueWhy: Without explicit network segmentation, every service can communicate with every other service. A compromised proxy container can directly access the database. ALWAYS use separate networks to enforce least-privilege communication.
Assigning static IPs without IPAM configuration
# WRONG -- static IP without subnet definition
services:
dns:
networks:
infra:
ipv4_address: 172.20.0.53
networks:
infra:# CORRECT -- IPAM subnet defined for static IP range
services:
dns:
networks:
infra:
ipv4_address: 172.20.0.53
networks:
infra:
ipam:
config:
- subnet: 172.20.0.0/16Why: Without IPAM configuration, Compose cannot validate the static IP or ensure it falls within an allocatable range. ALWAYS define ipam.config with a subnet when using static IP addresses.
Combining network_mode with networks
# WRONG -- network_mode and networks are mutually exclusive
services:
monitor:
network_mode: host
networks:
- monitoring# CORRECT -- use one or the other
services:
monitor:
network_mode: hostWhy: network_mode and networks are mutually exclusive. Using both causes a Compose validation error. NEVER combine them in the same service.
Not marking backend networks as internal
# WRONG -- database network has internet access
networks:
backend:
driver: bridge# CORRECT -- internal network prevents internet access
networks:
backend:
driver: bridge
internal: trueWhy: Without internal: true, containers on the network can reach the internet. Database and cache containers NEVER need internet access. ALWAYS set internal: true on networks that should not have external connectivity.
---
Volume Anti-Patterns
Using anonymous volumes for persistent data
# WRONG -- anonymous volume, data lost on docker compose down
services:
db:
image: postgres
volumes:
- /var/lib/postgresql/data# CORRECT -- named volume persists across lifecycle operations
services:
db:
image: postgres
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:Why: Anonymous volumes are NOT preserved by docker compose down. Named volumes persist across container recreation. ALWAYS use named volumes for any data that must survive container lifecycle operations.
Referencing undeclared named volumes
# WRONG -- volume used in service but not declared at top level
services:
db:
volumes:
- db-data:/var/lib/postgresql/data# CORRECT -- volume declared at top level
services:
db:
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:Why: Compose requires every named volume used in a service to be declared in the top-level volumes section. Omitting the declaration causes a validation error. ALWAYS declare every named volume.
Using bind mounts for database storage in production
# WRONG -- bind mount ties data to specific host path
services:
db:
image: postgres
volumes:
- ./data/postgres:/var/lib/postgresql/data# CORRECT -- named volume with appropriate driver
services:
db:
image: postgres
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:Why: Bind mounts create tight coupling to the host filesystem, have permission issues across platforms, and do not benefit from Docker's volume driver capabilities (snapshots, replication, backup). ALWAYS use named volumes for database storage. Reserve bind mounts for development-time source code mounting.
Not using read-only mounts where appropriate
# WRONG -- backup service has write access to source data
services:
backup:
volumes:
- app-data:/data# CORRECT -- read-only mount prevents accidental writes
services:
backup:
volumes:
- app-data:/data:roWhy: Without :ro, every mounted service has full write access. A bug in the backup service could corrupt the data. ALWAYS mount volumes as read-only when the service only needs to read.
---
Config Anti-Patterns
Using environment variables for multi-line configuration
# WRONG -- complex config crammed into environment variable
services:
web:
environment:
NGINX_CONFIG: |
server {
listen 80;
location / { proxy_pass http://app:8080; }
}# CORRECT -- use a config for structured configuration files
services:
web:
configs:
- source: nginx-config
target: /etc/nginx/conf.d/default.conf
configs:
nginx-config:
file: ./nginx/default.confWhy: Environment variables are designed for simple key-value pairs, not structured file content. Multi-line values in environment variables are fragile, hard to debug, and do not support proper file permissions. ALWAYS use configs for configuration files.
Not specifying target path for configs
# WRONG -- config mounted at root as /<config-name>
services:
web:
configs:
- nginx-config# CORRECT -- explicit target path where application expects it
services:
web:
configs:
- source: nginx-config
target: /etc/nginx/nginx.conf
mode: 0440Why: The default mount path (/<config-name>) is rarely where an application expects its configuration file. ALWAYS specify target to mount the config at the correct application path.
Combining mutually exclusive config sources
# WRONG -- file and content cannot coexist
configs:
app-config:
file: ./config.json
content: |
{"key": "value"}# CORRECT -- use only one source per config
configs:
app-config:
file: ./config.json
inline-config:
content: |
{"key": "value"}Why: Each config definition accepts exactly ONE source: file, environment, content, or external. Specifying multiple sources causes a validation error. ALWAYS use a single source per config.
---
Secret Anti-Patterns
Using environment variables for sensitive data
# WRONG -- password visible in docker inspect, logs, process listing
services:
db:
environment:
POSTGRES_PASSWORD: "super-secret-password"# CORRECT -- secret mounted as file, not visible in inspect/logs
services:
db:
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db-password
secrets:
- db-password
secrets:
db-password:
file: ./secrets/db_password.txtWhy: Environment variables are visible via docker inspect, process listings, and debug logs. Secrets are mounted as files with controlled permissions and are NOT exposed through container metadata. ALWAYS use secrets for passwords, API keys, certificates, and tokens.
Using default permissions on secret files
# WRONG -- default 0444 is world-readable inside container
services:
web:
secrets:
- source: tls-key
target: /etc/ssl/private/server.key# CORRECT -- restrictive permissions on private key
services:
web:
secrets:
- source: tls-key
target: /etc/ssl/private/server.key
uid: "103"
gid: "103"
mode: 0400Why: The default mode 0444 means any process in the container can read the secret. For private keys and credentials, ALWAYS set mode: 0400 (owner-read-only) or mode: 0440 (owner+group read) with explicit uid/gid.
Committing secret files to version control
# WRONG -- secret file tracked in Git
secrets:
db-password:
file: ./db_password.txt # This file is committed to the repo!# CORRECT -- use environment source in CI/CD
secrets:
db-password:
environment: "DB_PASSWORD"
# OR use file source with proper .gitignore
secrets:
db-password:
file: ./secrets/db_password.txt # ./secrets/ in .gitignoreWhy: Secret files committed to version control are permanently exposed in Git history. ALWAYS add secret file paths to .gitignore. In CI/CD pipelines, ALWAYS use the environment source to inject secrets from the pipeline's secret management system.
---
External Resource Anti-Patterns
Adding creation attributes to external resources
# WRONG -- driver_opts ignored on external volumes
volumes:
shared-data:
external: true
driver: local
driver_opts:
type: nfs
o: "addr=10.0.0.1"# CORRECT -- only name is valid with external
volumes:
shared-data:
external: true
name: "nfs-shared-data"Why: When external: true is set, Compose does NOT create the resource. All creation attributes (driver, driver_opts, labels, ipam, file, content) are ignored or rejected. ALWAYS configure external resources outside of Compose and reference them by name only.
Not validating external resources before deployment
# WRONG -- Compose fails at startup if resource is missing
volumes:
shared-data:
external: true# CORRECT -- create external resources before running Compose
# Pre-deployment script:
# docker volume create shared-data
# docker network create shared-net
volumes:
shared-data:
external: trueWhy: Compose does NOT create external resources and errors immediately if they are missing. ALWAYS ensure external networks, volumes, configs, and secrets exist before running docker compose up. Include resource creation in deployment scripts or infrastructure-as-code.
---
General Resource Anti-Patterns
Not using labels for resource management
# WRONG -- no labels, difficult to identify and manage
volumes:
db-data:
cache-data:
uploads:
networks:
frontend:
backend:# CORRECT -- labeled for identification and automation
volumes:
db-data:
labels:
com.example.project: "myapp"
com.example.component: "database"
com.example.backup: "daily"
cache-data:
labels:
com.example.project: "myapp"
com.example.component: "cache"
com.example.backup: "none"
networks:
frontend:
labels:
com.example.project: "myapp"
com.example.tier: "public"
backend:
labels:
com.example.project: "myapp"
com.example.tier: "private"Why: Without labels, resources from different projects become indistinguishable. Labels enable filtering (docker volume ls --filter label=com.example.backup=daily), automated management, and documentation. ALWAYS use reverse-DNS labeled resources in production.
Using project-prefixed names when sharing across projects
# WRONG -- each project creates its own copy
# Project A:
volumes:
shared-data: # Creates projecta_shared-data
# Project B:
volumes:
shared-data: # Creates projectb_shared-data (different volume!)# CORRECT -- use name or external for cross-project sharing
# Project A:
volumes:
shared-data:
name: "global-shared-data"
# Project B:
volumes:
shared-data:
external: true
name: "global-shared-data"Why: Compose prefixes resource names with the project name by default. Two projects declaring the same volume key get separate volumes. ALWAYS use name or external: true with name when resources must be shared across Compose projects.
Official Sources
- https://docs.docker.com/compose/compose-file/06-networks/
- https://docs.docker.com/compose/compose-file/07-volumes/
- https://docs.docker.com/compose/compose-file/08-configs/
- https://docs.docker.com/compose/compose-file/09-secrets/
Networks Reference
Top-Level Network Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
driver | string | bridge | Network driver: bridge, host, overlay, macvlan, none |
driver_opts | map | — | Driver-specific options as key-value pairs |
ipam | object | — | IP Address Management configuration |
external | boolean | false | Network exists outside Compose lifecycle |
internal | boolean | false | Restrict external access (no internet) |
attachable | boolean | false | Allow standalone containers to attach |
enable_ipv4 | boolean | true | Enable IPv4 networking |
enable_ipv6 | boolean | false | Enable IPv6 networking |
labels | map/list | — | Metadata labels (reverse-DNS notation recommended) |
name | string | <project>_<key> | Custom network name (supports interpolation) |
Network Drivers
bridge (Default)
Creates an isolated network on a single Docker host. Containers on the same bridge network can communicate by service name. This is the default driver when no driver is specified.
networks:
app-net:
driver: bridge
driver_opts:
com.docker.network.bridge.host_binding_ipv4: "127.0.0.1"
com.docker.network.bridge.enable_icc: "true"
com.docker.network.bridge.enable_ip_masquerade: "true"
com.docker.network.bridge.name: "br-custom"
com.docker.network.driver.mtu: "1500"ALWAYS use bridge for single-host development and production setups where containers run on the same machine.
host
Removes network isolation -- the container shares the host's networking namespace directly. No port mapping is needed or possible.
networks:
hostnet:
driver: hostALWAYS use host only when container needs maximum network performance or must bind to host ports directly. NEVER use host driver when network isolation between containers is required.
overlay
Enables multi-host networking for Docker Swarm services. Containers on different hosts can communicate as if on the same network.
networks:
swarm-net:
driver: overlay
attachable: true # Allow non-Swarm containers to attachALWAYS use overlay when services span multiple Docker hosts in a Swarm cluster.
macvlan
Assigns a MAC address to each container, making it appear as a physical device on the network. Containers get their own IP on the physical network.
networks:
physical-net:
driver: macvlan
driver_opts:
parent: eth0
ipam:
config:
- subnet: 192.168.1.0/24
gateway: 192.168.1.1ALWAYS use macvlan when containers need to appear as physical devices on the LAN (e.g., DHCP servers, network appliances).
none
Completely disables networking for the container.
networks:
no-net:
driver: noneALWAYS use none when a container must have zero network access for security isolation.
IPAM Configuration
IPAM (IP Address Management) controls how IP addresses are assigned to containers on a network.
Full IPAM Structure
networks:
custom-net:
ipam:
driver: default
config:
- subnet: 172.28.0.0/16
ip_range: 172.28.5.0/24
gateway: 172.28.5.254
aux_addresses:
host1: 172.28.1.5
host2: 172.28.1.6IPAM Elements
| Element | Type | Description |
|---|---|---|
driver | string | IPAM driver (default: default) |
config | list | List of IPAM configuration blocks |
config[].subnet | string | CIDR-formatted network segment |
config[].ip_range | string | Allocatable container IP range within the subnet |
config[].gateway | string | IPv4 or IPv6 gateway for master subnet |
config[].aux_addresses | map | Auxiliary addresses mapped to hostnames (reserved IPs) |
Dual-Stack (IPv4 + IPv6) IPAM
networks:
dual-stack:
enable_ipv6: true
ipam:
config:
- subnet: 172.28.0.0/16
gateway: 172.28.0.1
- subnet: 2001:db8::/64
gateway: 2001:db8::1IPv6-Only Network
networks:
ipv6-only:
enable_ipv4: false
enable_ipv6: true
ipam:
config:
- subnet: 2001:db8::/64External Networks
External networks are NOT created or destroyed by Compose. They must exist before running docker compose up.
networks:
# Simple external reference
existing-net:
external: true
# External with custom name
app-net:
external: true
name: "production-network"
# External with variable interpolation
dynamic-net:
external: true
name: "${NETWORK_ID}"ALWAYS create external networks before running Compose:
docker network create production-networkNEVER use driver, driver_opts, ipam, internal, attachable, or labels alongside external: true -- only name is valid with external networks.
Customizing the Default Network
Every Compose project gets an implicit default network. Override it by defining a network named default:
networks:
default:
name: my-project-network
driver: bridge
driver_opts:
com.docker.network.bridge.host_binding_ipv4: "127.0.0.1"
ipam:
config:
- subnet: 172.30.0.0/16Service-Level Network Options
Full Service Network Syntax
services:
app:
networks:
frontend:
aliases:
- webapp
- api-server
ipv4_address: 172.16.238.10
ipv6_address: 2001:3984:3989::10
link_local_ips:
- 169.254.0.10
mac_address: "02:42:ac:11:65:43"
interface_name: eth1
priority: 1000
gw_priority: 100
driver_opts:
com.example.custom: "value"Service Network Attributes
| Attribute | Type | Description |
|---|---|---|
aliases | list | Additional hostnames for DNS resolution on this network |
ipv4_address | string | Static IPv4 address (requires IPAM subnet config) |
ipv6_address | string | Static IPv6 address (requires IPAM subnet + enable_ipv6) |
link_local_ips | list | Link-local IP assignments |
mac_address | string | MAC address for this network connection |
interface_name | string | Name of the network interface in the container |
priority | integer | Connection order (higher connects first) |
gw_priority | integer | Default gateway selection (higher value wins) |
driver_opts | map | Driver-specific options for this connection |
Network Aliases
Aliases provide additional DNS names for a service on a specific network. Other containers on the same network can reach the service using any of its aliases.
services:
database:
image: postgres
networks:
backend:
aliases:
- db
- postgres
- primary-dbALWAYS use aliases when multiple services need to reference another service by different names, or when migrating from one service name to another.
Static IP Assignment
Static IPs require a matching IPAM subnet configuration:
services:
dns:
image: coredns
networks:
infra:
ipv4_address: 172.20.0.53
networks:
infra:
ipam:
config:
- subnet: 172.20.0.0/16NEVER assign static IPs without defining the subnet in IPAM -- Compose cannot validate the address without a known subnet range.
Network Isolation Pattern
services:
proxy:
networks: [frontend]
app:
networks: [frontend, backend]
db:
networks: [backend]
cache:
networks: [backend]
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: trueproxycan ONLY reachapp(sharedfrontendnetwork)dbandcachecan ONLY reachapp(sharedbackendnetwork)proxyCANNOT reachdborcache(no shared network)backendhasinternal: true-- containers on it cannot reach the internet
network_mode (Service-Level)
network_mode overrides the default network assignment entirely:
services:
monitor:
network_mode: "host" # Share host network
isolated:
network_mode: "none" # No networking
sidecar:
network_mode: "service:app" # Share network namespace with another service
legacy:
network_mode: "container:abc123" # Share with specific containerNEVER combine network_mode with networks -- they are mutually exclusive. Using both causes a Compose validation error.
Official Sources
- https://docs.docker.com/compose/compose-file/06-networks/
- https://docs.docker.com/engine/network/
- https://docs.docker.com/engine/network/drivers/
Volumes, Configs, and Secrets Reference
Top-Level Volumes
Volume Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
driver | string | local | Volume driver |
driver_opts | map | — | Driver-specific options as key-value pairs |
external | boolean | false | Volume exists outside Compose lifecycle |
labels | map/list | — | Metadata labels (reverse-DNS notation recommended) |
name | string | <project>_<key> | Custom volume name (supports interpolation) |
Basic Named Volume
volumes:
db-data:An empty declaration creates a volume using the default local driver. The volume persists across docker compose down and is reused on subsequent docker compose up.
ALWAYS declare named volumes at the top level -- referencing an undeclared volume name in a service causes a validation error.
Volume with Custom Driver
volumes:
db-data:
driver: local
driver_opts:
type: "none"
o: "bind"
device: "/data/db"NFS Volume
volumes:
nfs-data:
driver_opts:
type: "nfs"
o: "addr=10.40.0.199,nolock,soft,rw"
device: ":/docker/example"CIFS/SMB Volume
volumes:
smb-data:
driver_opts:
type: "cifs"
o: "addr=10.40.0.199,username=user,password=pass"
device: "//10.40.0.199/share"tmpfs Volume
volumes:
tmp-data:
driver_opts:
type: "tmpfs"
device: "tmpfs"
o: "size=100m,uid=1000"External Volume
volumes:
shared-data:
external: true
# With custom name
db-data:
external: true
name: actual-volume-name
# With variable interpolation
dynamic-vol:
external: true
name: "${VOLUME_NAME}"NEVER use driver, driver_opts, or labels alongside external: true -- only name is valid with external volumes.
ALWAYS create external volumes before running Compose:
docker volume create actual-volume-nameVolume Labels
volumes:
db-data:
labels:
com.example.description: "Database volume"
com.example.department: "IT/Ops"
com.example.backup: "daily"Labels apply to named volumes ONLY, NOT to bind mounts. Visible via docker volume inspect.
Custom Volume Name
volumes:
db-data:
name: "my-app-data"
# With interpolation
cache:
name: "${PROJECT_NAME}_cache"Service-Level Volume Mounting
Short Syntax
Format: [SOURCE:]TARGET[:ACCESS_MODE]
services:
app:
volumes:
- db-data:/var/lib/postgresql/data # Named volume
- /host/path:/container/path # Bind mount
- ./relative:/container/path # Relative bind mount
- ~/home/path:/container/path # Home directory
- /container/anonymous # Anonymous volume
- db-data:/data:ro # Read-only
- db-data:/data:rw # Read-write (default)Long Syntax
services:
app:
volumes:
# Named volume
- type: volume
source: db-data
target: /var/lib/postgresql/data
volume:
nocopy: true # Do not copy data from container on creation
subpath: sub # Mount a subdirectory of the volume
# Bind mount
- type: bind
source: /host/data
target: /container/data
read_only: true
bind:
propagation: rprivate # Mount propagation
create_host_path: true # Create host path if missing
selinux: z # SELinux relabeling (z=shared, Z=private)
# tmpfs mount
- type: tmpfs
target: /tmp
tmpfs:
size: 1073741824 # 1GB in bytes
mode: 0755
# Named pipe (Windows)
- type: npipe
source: \\.\pipe\docker_engine
target: \\.\pipe\docker_engineVolume Mount Attributes (Long Syntax)
| Attribute | Type | Description |
|---|---|---|
type | string | volume, bind, tmpfs, npipe, cluster |
source | string | Volume name or host path |
target | string | Container mount path |
read_only | boolean | Mount as read-only |
volume.nocopy | boolean | Do not copy container data on first mount |
volume.subpath | string | Mount subdirectory of a volume |
bind.propagation | string | rprivate, private, rshared, shared, rslave, slave |
bind.create_host_path | boolean | Create host path if it does not exist |
bind.selinux | string | z (shared) or Z (private) SELinux relabeling |
tmpfs.size | integer | Size in bytes |
tmpfs.mode | integer | File mode (e.g., 0755) |
volumes_from
Mount all volumes from another service or container:
services:
backup:
volumes_from:
- db # All volumes from db service
- db:ro # Read-only
- container:legacy_db:rw # From external containerSharing Volumes Between Services
services:
writer:
image: myapp
volumes:
- shared-data:/data
reader:
image: backup
volumes:
- shared-data:/var/lib/backup/data:ro
volumes:
shared-data:ALWAYS use :ro (read-only) on the consumer service when it only needs to read the data -- this prevents accidental writes.
---
Top-Level Configs
Config Attributes
| Attribute | Type | Description |
|---|---|---|
file | string | Path to file containing config data |
environment | string | Environment variable name holding config value (Compose 2.23.1+) |
content | string | Inline config content with interpolation (Compose 2.23.1+) |
external | boolean | Config exists outside Compose lifecycle |
name | string | Custom config name in the platform |
Config Sources
Only ONE source attribute (file, environment, content, or external) can be used per config definition.
File-Based Config
configs:
nginx-config:
file: ./nginx/nginx.confEnvironment-Based Config (Compose 2.23.1+)
configs:
app-config:
environment: "APP_CONFIG_JSON"The value of the environment variable becomes the config content.
Inline Content Config (Compose 2.23.1+)
configs:
app-properties:
content: |
debug=${DEBUG}
spring.application.name=${COMPOSE_PROJECT_NAME}
server.port=8080Supports Compose variable interpolation within the content.
External Config
configs:
shared-config:
external: true
name: "${HTTP_CONFIG_KEY}"Mounting Configs in Services
Short Syntax
services:
web:
configs:
- nginx-config # Mounts at /<config-name>
- app-properties # Mounts at /app-propertiesLong Syntax
services:
web:
configs:
- source: nginx-config
target: /etc/nginx/nginx.conf
uid: "1000"
gid: "1000"
mode: 0440Config Mount Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
source | string | — | Config name as declared in top-level configs |
target | string | /<config-name> | Mount path inside the container |
uid | string | Container command user | File owner UID |
gid | string | Container command user | File group GID |
mode | integer | 0444 | File permissions |
---
Top-Level Secrets
Secret Attributes
| Attribute | Type | Description |
|---|---|---|
file | string | Path to file containing secret data |
environment | string | Environment variable name holding secret value |
external | boolean | Secret exists outside Compose lifecycle |
name | string | Custom secret name in the platform |
Secret Sources
Only ONE source attribute (file, environment, or external) can be used per secret definition.
File-Based Secret
secrets:
server-cert:
file: ./certs/server.crt
server-key:
file: ./certs/server.key
db-password:
file: ./secrets/db_password.txtEnvironment-Based Secret
secrets:
oauth-token:
environment: "OAUTH_TOKEN"
api-key:
environment: "API_KEY"ALWAYS prefer environment source for CI/CD pipelines where secrets are injected as environment variables. ALWAYS prefer file source for local development with secret files excluded from version control.
External Secret
secrets:
production-cert:
external: true
name: "${CERT_SECRET_NAME}"Mounting Secrets in Services
Short Syntax
services:
web:
secrets:
- server-cert # Mounts at /run/secrets/server-cert
- db-password # Mounts at /run/secrets/db-passwordLong Syntax
services:
web:
secrets:
- source: server-cert
target: /etc/ssl/certs/server.crt
uid: "103"
gid: "103"
mode: 0440
- source: server-key
target: /etc/ssl/private/server.key
uid: "103"
gid: "103"
mode: 0400Secret Mount Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
source | string | — | Secret name as declared in top-level secrets |
target | string | /run/secrets/<name> | Mount path inside the container |
uid | string | Container command user | File owner UID |
gid | string | Container command user | File group GID |
mode | integer | 0444 | File permissions |
ALWAYS set restrictive mode on secret files (e.g., 0400 or 0440) -- the default 0444 is world-readable inside the container.
---
Resource Naming Conventions
Default Naming
Compose prefixes resource names with the project name:
| Resource | Default Name Pattern |
|---|---|
| Network | <project>_<network-key> |
| Volume | <project>_<volume-key> |
| Config | <project>_<config-key> |
| Secret | <project>_<secret-key> |
Custom Naming with name
networks:
app-net:
name: "production-network" # Exact name, no project prefix
volumes:
db-data:
name: "postgres-data-v2" # Exact name, no project prefix
configs:
app-config:
name: "app-config-${ENV}" # Interpolation supported
secrets:
tls-cert:
name: "tls-cert-${DOMAIN}" # Interpolation supportedALWAYS use the name attribute when resources must have predictable names (e.g., for external references from other projects or scripts).
Official Sources
- https://docs.docker.com/compose/compose-file/07-volumes/
- https://docs.docker.com/compose/compose-file/08-configs/
- https://docs.docker.com/compose/compose-file/09-secrets/
- https://docs.docker.com/compose/compose-file/05-services/