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:
2025-12-30 16:39:42 -05:00
commit e769526b13
3 changed files with 584 additions and 0 deletions

168
SKILL.md Normal file
View 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
View 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
View 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;
```