Initial commit: docker-compose-config skill
Add skill for Docker Compose configuration management with: - Multi-host environment structure with per-hostname overrides - External volume mount patterns with env variable substitution - Reverse proxy network configuration for SWAG integration - Reference docs for examples and network topology 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
168
SKILL.md
Normal file
168
SKILL.md
Normal file
@@ -0,0 +1,168 @@
|
||||
---
|
||||
name: docker-compose-config
|
||||
description: Docker Compose configuration management for multi-host server deployments. Use when creating, modifying, or managing docker-compose.yml files with environment-specific configurations, external volume mounts, and reverse proxy networks. Triggers on tasks involving Docker Compose files, environment overrides, multi-host deployments, or service configuration for self-hosted applications.
|
||||
---
|
||||
|
||||
# Docker Compose Configuration Management
|
||||
|
||||
This skill provides guidance for managing Docker Compose configurations across multiple server environments with per-host overrides.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
Each project follows this structure:
|
||||
|
||||
```
|
||||
/docker/config/
|
||||
├── <project>/
|
||||
│ ├── docker-compose.yml # Main service definitions
|
||||
│ ├── docker-compose.override.yml # Current host overrides (gitignored)
|
||||
│ ├── .env # Environment variables (gitignored)
|
||||
│ └── environments/
|
||||
│ └── <hostname>/ # Per-host configs
|
||||
│ ├── .env
|
||||
│ └── docker-compose.override.<hostname>.yml
|
||||
```
|
||||
|
||||
## Compose File Conventions
|
||||
|
||||
### Service Definition Pattern
|
||||
|
||||
```yaml
|
||||
name: <project-name>
|
||||
|
||||
services:
|
||||
<service-name>:
|
||||
container_name: <service_container>
|
||||
image: <registry>/<image>:<version>@sha256:<digest>
|
||||
environment:
|
||||
PUID: ${UID}
|
||||
PGID: ${GID}
|
||||
# Service-specific vars use env substitution
|
||||
VAR_NAME: ${VAR_NAME}
|
||||
volumes:
|
||||
- ${DATA_PATH}:/app/data
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- ${SERVICE_PORT:-default}:internal_port
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- default
|
||||
- reverse_proxy
|
||||
|
||||
networks:
|
||||
reverse_proxy:
|
||||
name: reverse_proxy
|
||||
attachable: true
|
||||
```
|
||||
|
||||
### Key Patterns
|
||||
|
||||
1. **Image pinning**: Use SHA256 digests for reproducibility
|
||||
```yaml
|
||||
image: ghcr.io/org/image:v1.0.0@sha256:abc123...
|
||||
```
|
||||
|
||||
2. **Port defaults**: Always provide defaults for ports
|
||||
```yaml
|
||||
ports:
|
||||
- ${APP_PORT:-8080}:8080
|
||||
```
|
||||
|
||||
3. **Volume mounts**: Use environment variables for paths
|
||||
```yaml
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
- ${DB_DATA_LOCATION}/data:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
4. **Proxy network**: Services needing reverse proxy access join `reverse_proxy` network
|
||||
```yaml
|
||||
networks:
|
||||
- default
|
||||
- reverse_proxy
|
||||
|
||||
networks:
|
||||
reverse_proxy:
|
||||
name: reverse_proxy
|
||||
attachable: true
|
||||
```
|
||||
|
||||
## Environment File Conventions
|
||||
|
||||
### Structure (.env)
|
||||
|
||||
```bash
|
||||
# Permissions
|
||||
UID=1000
|
||||
GID=1000
|
||||
|
||||
# Paths
|
||||
UPLOAD_LOCATION=/mnt/user/pictures
|
||||
DB_DATA_LOCATION=/mnt/user/appdata/<project>
|
||||
|
||||
# Ports
|
||||
APP_PORT=8080
|
||||
|
||||
# Secrets
|
||||
DB_PASSWORD=<generated>
|
||||
|
||||
# Database
|
||||
DB_HOSTNAME=database
|
||||
DB_USERNAME=postgres
|
||||
DB_DATABASE_NAME=<project>
|
||||
```
|
||||
|
||||
### Per-Host Variations
|
||||
|
||||
- `environments/<hostname>/.env` overrides paths for specific hosts
|
||||
- Common overrides: mount paths (`/mnt/user/` vs `/mnt/main/`), ports
|
||||
|
||||
## Common Commands
|
||||
|
||||
```bash
|
||||
# Start a project
|
||||
docker compose -f <project>/docker-compose.yml up -d
|
||||
|
||||
# Start with host-specific override
|
||||
docker compose -f <project>/docker-compose.yml \
|
||||
-f <project>/environments/<host>/docker-compose.override.<host>.yml up -d
|
||||
|
||||
# View logs
|
||||
docker compose -f <project>/docker-compose.yml logs -f
|
||||
|
||||
# Stop services
|
||||
docker compose -f <project>/docker-compose.yml down
|
||||
```
|
||||
|
||||
## Adding a New Host Environment
|
||||
|
||||
1. Create `environments/<hostname>/` directory
|
||||
2. Copy existing host's `.env` as template
|
||||
3. Update paths and port mappings for the host
|
||||
4. Create override compose file if device mappings differ
|
||||
|
||||
## Service Dependencies
|
||||
|
||||
Use `depends_on` with health checks for proper startup order:
|
||||
|
||||
```yaml
|
||||
depends_on:
|
||||
database:
|
||||
condition: service_healthy
|
||||
```
|
||||
|
||||
## Healthchecks
|
||||
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/"]
|
||||
start_period: 5m
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- See [examples.md](references/examples.md) for complete service configurations
|
||||
- See [networks.md](references/networks.md) for network topology details
|
||||
238
references/examples.md
Normal file
238
references/examples.md
Normal file
@@ -0,0 +1,238 @@
|
||||
# Docker Compose Configuration Examples
|
||||
|
||||
## Multi-Service Application (Immich-style)
|
||||
|
||||
Complete example with app server, machine learning, Redis, and PostgreSQL:
|
||||
|
||||
```yaml
|
||||
name: immich
|
||||
|
||||
services:
|
||||
immich-server:
|
||||
container_name: immich_server
|
||||
image: ghcr.io/immich-app/immich-server:v2.3.1
|
||||
environment:
|
||||
DB_USERNAME: ${DB_USERNAME}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
DB_DATABASE_NAME: ${DB_DATABASE_NAME}
|
||||
REDIS_HOSTNAME: immich_redis
|
||||
volumes:
|
||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
ports:
|
||||
- ${APP_PORT:-2283}:2283
|
||||
depends_on:
|
||||
- redis
|
||||
- database
|
||||
restart: always
|
||||
networks:
|
||||
- default
|
||||
- reverse_proxy
|
||||
|
||||
immich-machine-learning:
|
||||
container_name: immich_machine_learning
|
||||
image: ghcr.io/immich-app/immich-machine-learning:v2.3.1
|
||||
volumes:
|
||||
- model-cache:/cache
|
||||
env_file:
|
||||
- .env
|
||||
restart: always
|
||||
networks:
|
||||
- default
|
||||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: registry.redict.io/redict:7.3.6@sha256:2a99f322eed7...
|
||||
restart: always
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
networks:
|
||||
- default
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: ghcr.io/immich-app/postgres:16-vectorchord0.3.0@sha256:5b434f184ec...
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_USER: ${DB_USERNAME}
|
||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||
POSTGRES_INITDB_ARGS: '--data-checksums'
|
||||
volumes:
|
||||
- ${DB_DATA_LOCATION}/data:/var/lib/postgresql/data
|
||||
restart: always
|
||||
networks:
|
||||
- default
|
||||
|
||||
volumes:
|
||||
model-cache:
|
||||
redis-data:
|
||||
|
||||
networks:
|
||||
reverse_proxy:
|
||||
name: reverse_proxy
|
||||
attachable: true
|
||||
```
|
||||
|
||||
## Reverse Proxy Service (SWAG-style)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
swag:
|
||||
image: lscr.io/linuxserver/swag:5.2.2@sha256:c8afbd137c2f...
|
||||
container_name: swag
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
environment:
|
||||
- PUID=${PUID}
|
||||
- PGID=${PGID}
|
||||
- TZ=America/New_York
|
||||
- URL=${SWAG_URL}
|
||||
- VALIDATION=dns
|
||||
- SUBDOMAINS=wildcard
|
||||
- DNSPLUGIN=cloudflare
|
||||
- PROPAGATION=20
|
||||
- EMAIL=user@domain.com
|
||||
- ONLY_SUBDOMAINS=${ONLY_SUBDOMAINS:-false}
|
||||
- EXTRA_DOMAINS=${EXTRA_DOMAINS:-}
|
||||
- STAGING=false
|
||||
volumes:
|
||||
- ${SWAG_DATA_PATH}:/config
|
||||
ports:
|
||||
- 1443:443
|
||||
- 81:80
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- default
|
||||
- reverse_proxy
|
||||
|
||||
networks:
|
||||
reverse_proxy:
|
||||
name: reverse_proxy
|
||||
attachable: true
|
||||
```
|
||||
|
||||
## Media Server with GPU (Jellyfin-style)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
jellyfin:
|
||||
image: lscr.io/linuxserver/jellyfin:10.11.4@sha256:234ea8d508b2...
|
||||
container_name: jellyfin
|
||||
runtime: nvidia
|
||||
environment:
|
||||
PUID: ${UID}
|
||||
PGID: ${GID}
|
||||
UMASK: "022"
|
||||
JELLYFIN_PublishedServerUrl: ${HOST_IP}
|
||||
NVIDIA_VISIBLE_DEVICES: all
|
||||
ports:
|
||||
- ${JELLYFIN_PORT:-8096}:8096
|
||||
- ${JELLYFIN_LOCAL_PORT:-7359}:7359
|
||||
- ${JELLYFIN_DNLA_PORT:-1900}:1900
|
||||
volumes:
|
||||
- ${MEDIA_PATH}:/data/media
|
||||
- ${BASE_SERVER_PATH}/jellyfin:/config
|
||||
- ${TRANSCODE_PATH}jellyfin:/config/cache/transcodes
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://127.0.0.1:8096/"]
|
||||
start_period: 5m
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 10
|
||||
```
|
||||
|
||||
## Service with VPN (Deluge-style)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
deluge:
|
||||
image: binhex/arch-delugevpn:latest@sha256:2ff474cba3af...
|
||||
container_name: deluge
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
environment:
|
||||
PUID: ${UID}
|
||||
PGID: ${GID}
|
||||
UMASK: "000"
|
||||
NAME_SERVERS: ${VPN_NAME_SERVERS}
|
||||
DEBUG: false
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ${SEEDBOX_PATH}:/data
|
||||
- ${BASE_SERVER_PATH}/deluge:/config
|
||||
ports:
|
||||
- ${DELUGE_PORT_1:-8112}:8112
|
||||
- ${DELUGE_PORT_2:-58846}:58846
|
||||
```
|
||||
|
||||
## Database with Healthcheck (PostgreSQL-style)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
database:
|
||||
image: postgres:18.1@sha256:5ec39c188013...
|
||||
container_name: app-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER:-appuser}
|
||||
POSTGRES_PASSWORD: ${DB_PASS}
|
||||
ports:
|
||||
- ${DB_PORT:-5432}:5432
|
||||
volumes:
|
||||
- ${BASE_SERVER_PATH}/app/data:/var/lib/postgresql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -d postgres -U $${DB_USER:-appuser}"]
|
||||
start_period: 20s
|
||||
interval: 30s
|
||||
retries: 5
|
||||
timeout: 5s
|
||||
```
|
||||
|
||||
## Environment File Examples
|
||||
|
||||
### Main .env
|
||||
|
||||
```bash
|
||||
# Permissions
|
||||
UID=1000
|
||||
GID=1000
|
||||
|
||||
# Paths
|
||||
UPLOAD_LOCATION=/mnt/main/pictures
|
||||
DB_DATA_LOCATION=/docker/appdata/immich
|
||||
|
||||
# Ports
|
||||
APP_PORT=2283
|
||||
|
||||
# Secrets
|
||||
DB_PASSWORD=GeneratedSecurePassword123
|
||||
|
||||
# Database
|
||||
DB_HOSTNAME=database
|
||||
DB_USERNAME=postgres
|
||||
DB_DATABASE_NAME=immich
|
||||
```
|
||||
|
||||
### Host-specific .env (environments/mabel/.env)
|
||||
|
||||
```bash
|
||||
# Permissions (same across hosts)
|
||||
UID=1000
|
||||
GID=1000
|
||||
|
||||
# Paths (host-specific mount points)
|
||||
UPLOAD_LOCATION=/mnt/user/pictures
|
||||
DB_DATA_LOCATION=/mnt/user/appdata/immich
|
||||
|
||||
# Ports (may vary by host)
|
||||
APP_PORT=2283
|
||||
|
||||
# Secrets (same across hosts)
|
||||
DB_PASSWORD=GeneratedSecurePassword123
|
||||
|
||||
# Database
|
||||
DB_HOSTNAME=database
|
||||
DB_USERNAME=postgres
|
||||
DB_DATABASE_NAME=immich
|
||||
```
|
||||
178
references/networks.md
Normal file
178
references/networks.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Docker Compose Network Configuration
|
||||
|
||||
## Network Topology
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ reverse_proxy network │
|
||||
│ (attachable, shared across all projects) │
|
||||
│ │
|
||||
│ ┌─────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ SWAG │ │ immich-srv │ │ grist-srv │ ... │
|
||||
│ │ :443/80 │ │ :2283 │ │ :8484 │ │
|
||||
│ └─────────┘ └─────────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│ │ │
|
||||
│ │ │
|
||||
External Internal Internal
|
||||
Traffic Services Services
|
||||
|
||||
┌───────────────────┐ ┌───────────────────┐
|
||||
│ immich_default │ │ grist_default │
|
||||
│ │ │ │
|
||||
│ ┌─────────────┐ │ │ ┌─────────────┐ │
|
||||
│ │ immich-ml │ │ │ │ grist │ │
|
||||
│ └─────────────┘ │ │ └─────────────┘ │
|
||||
│ ┌─────────────┐ │ │ ┌─────────────┐ │
|
||||
│ │ redis │ │ │ │ postgres │ │
|
||||
│ └─────────────┘ │ │ └─────────────┘ │
|
||||
│ ┌─────────────┐ │ └───────────────────┘
|
||||
│ │ postgres │ │
|
||||
│ └─────────────┘ │
|
||||
└───────────────────┘
|
||||
```
|
||||
|
||||
## Network Types
|
||||
|
||||
### 1. Project Default Network
|
||||
|
||||
Automatically created by Compose. Services within same project communicate here.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
networks:
|
||||
- default # Implicit, connects to <project>_default
|
||||
|
||||
database:
|
||||
networks:
|
||||
- default # Same network, can reach 'app' by service name
|
||||
```
|
||||
|
||||
### 2. Reverse Proxy Network
|
||||
|
||||
Shared external network for services needing reverse proxy access.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web-app:
|
||||
networks:
|
||||
- default # Internal communication
|
||||
- reverse_proxy # Accessible by SWAG
|
||||
|
||||
networks:
|
||||
reverse_proxy:
|
||||
name: reverse_proxy
|
||||
attachable: true
|
||||
```
|
||||
|
||||
### 3. Host Network Mode
|
||||
|
||||
For services requiring direct host network access (e.g., Plex discovery):
|
||||
|
||||
```yaml
|
||||
services:
|
||||
plex:
|
||||
network_mode: host
|
||||
# No 'ports' mapping needed - uses host ports directly
|
||||
```
|
||||
|
||||
## Network Configuration Patterns
|
||||
|
||||
### Standard Web Service
|
||||
|
||||
```yaml
|
||||
services:
|
||||
myapp:
|
||||
networks:
|
||||
- default
|
||||
- reverse_proxy
|
||||
|
||||
networks:
|
||||
reverse_proxy:
|
||||
name: reverse_proxy
|
||||
attachable: true
|
||||
```
|
||||
|
||||
### Internal-Only Service (Database)
|
||||
|
||||
```yaml
|
||||
services:
|
||||
postgres:
|
||||
networks:
|
||||
- default
|
||||
# No reverse_proxy - not externally accessible
|
||||
```
|
||||
|
||||
### Service with Multiple Networks
|
||||
|
||||
```yaml
|
||||
services:
|
||||
api-gateway:
|
||||
networks:
|
||||
- default
|
||||
- reverse_proxy
|
||||
- monitoring
|
||||
|
||||
networks:
|
||||
reverse_proxy:
|
||||
name: reverse_proxy
|
||||
attachable: true
|
||||
monitoring:
|
||||
name: monitoring
|
||||
attachable: true
|
||||
```
|
||||
|
||||
## Service Discovery
|
||||
|
||||
Services on the same network can reach each other by container name:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
container_name: myapp_server
|
||||
environment:
|
||||
REDIS_HOST: myapp_redis # Uses container_name
|
||||
DB_HOST: myapp_postgres
|
||||
|
||||
redis:
|
||||
container_name: myapp_redis
|
||||
|
||||
database:
|
||||
container_name: myapp_postgres
|
||||
```
|
||||
|
||||
## External Network Declaration
|
||||
|
||||
When connecting to pre-existing external networks:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
reverse_proxy:
|
||||
external: true
|
||||
name: reverse_proxy
|
||||
```
|
||||
|
||||
vs creating if not exists:
|
||||
|
||||
```yaml
|
||||
networks:
|
||||
reverse_proxy:
|
||||
name: reverse_proxy
|
||||
attachable: true
|
||||
```
|
||||
|
||||
## SWAG Proxy Configuration
|
||||
|
||||
Services on `reverse_proxy` network are accessible to SWAG at their container name and internal port:
|
||||
|
||||
| Service | Container Name | Internal URL |
|
||||
|---------|---------------|--------------|
|
||||
| Immich | immich_server | http://immich_server:2283 |
|
||||
| Grist | grist | http://grist:8484 |
|
||||
|
||||
SWAG nginx config example:
|
||||
```nginx
|
||||
upstream_app immich_server;
|
||||
upstream_port 2283;
|
||||
```
|
||||
Reference in New Issue
Block a user