Claude-skill-registry dokploy-multi-service
Multi-service architecture patterns for Dokploy templates including dependency chains, service communication, and complex stack design. Use when building templates with 2+ services.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/dokploy-multi-service" ~/.claude/skills/majiayu000-claude-skill-registry-dokploy-multi-service && rm -rf "$T"
skills/data/dokploy-multi-service/SKILL.mdDokploy Multi-Service Architecture
When to Use This Skill
- When creating templates with 2 or more services
- When designing service dependency chains
- When planning database + app + helper service stacks
- When user asks about "multi-container" or "service dependencies"
When NOT to Use This Skill
- For single-container applications
- For sidecar patterns (use dedicated sidecar documentation)
Prerequisites
- Understanding of service dependencies (what calls what)
- Knowledge of startup order requirements
- Understanding of internal vs external service access
Architecture Patterns
Pattern 1: App + Database (2-tier)
Structure:
┌─────────────┐ ┌─────────────┐ │ App │────▶│ Database │ │ (web UI) │ │ (internal) │ └─────────────┘ └─────────────┘ │ ▼ dokploy-network
Characteristics:
- App connects to both networks (external + internal)
- Database connects only to internal network
- App depends on database with
service_healthy
Example (Paaster + MongoDB):
services: paaster: image: wardpearce/paaster:3.1.7 depends_on: mongodb: condition: service_healthy networks: - paaster-net # Internal - dokploy-network # External (Traefik) # ... traefik labels, health check mongodb: image: mongo:7 networks: - paaster-net # Internal ONLY # ... health check, no traefik labels networks: paaster-net: driver: bridge dokploy-network: external: true
Pattern 2: App + Database + Cache (3-tier)
Structure:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ App │────▶│ Database │ │ Cache │ │ (web UI) │ │ (internal) │◀────│ (internal) │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ └───────────────────┴───────────────────┘ │ internal network
Dependency Logic:
services: app: depends_on: database: condition: service_healthy cache: condition: service_healthy
Example (Django + PostgreSQL + Redis):
services: app: image: myapp:1.0.0 depends_on: postgres: condition: service_healthy redis: condition: service_healthy environment: DATABASE_URL: postgresql://user:pass@postgres:5432/db REDIS_URL: redis://redis:6379 networks: - app-net - dokploy-network postgres: image: postgres:16-alpine networks: - app-net healthcheck: test: ["CMD-SHELL", "pg_isready -U user -d db"] redis: image: redis:7-alpine networks: - app-net healthcheck: test: ["CMD", "redis-cli", "ping"]
Pattern 3: App + Helpers (Star Pattern)
Structure:
┌─────────────┐ │ Helper 1 │ │ (on-demand) │ └──────▲──────┘ │ ┌─────────────┐ ┌──────┴──────┐ ┌─────────────┐ │ Database │◀────│ App │────▶│ Helper 2 │ │ (required) │ │ (main) │ │ (on-demand) │ └─────────────┘ └─────────────┘ └─────────────┘ │ ▼ dokploy-network
Dependency Logic:
- Database:
(required at startup)service_healthy - Helpers:
(called on-demand)service_started
Example (Paperless-ngx):
services: paperless: image: ghcr.io/paperless-ngx/paperless-ngx:2.13 depends_on: postgres: condition: service_healthy # Required at startup redis: condition: service_healthy # Required at startup gotenberg: condition: service_started # On-demand helper tika: condition: service_started # On-demand helper environment: PAPERLESS_TIKA_ENABLED: 1 PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 networks: - paperless-net - dokploy-network postgres: image: postgres:16-alpine networks: - paperless-net healthcheck: test: ["CMD-SHELL", "pg_isready -U paperless -d paperless"] redis: image: redis:7-alpine networks: - paperless-net healthcheck: test: ["CMD", "redis-cli", "ping"] gotenberg: image: gotenberg/gotenberg:8 networks: - paperless-net # No healthcheck - stateless converter tika: image: apache/tika:2.9.1.0 networks: - paperless-net # No healthcheck - stateless parser
Pattern 4: Multiple External Services
Structure:
┌─────────────┐ ┌─────────────┐ │ App │────▶│ Database │ │ (main) │ │ │ └─────────────┘ └─────────────┘ │ ▼ ┌─────────────┐ ┌─────────────┐ │ API │────▶│ Database │ │ (secondary) │ │ (shared) │ └─────────────┘ └─────────────┘ │ ▼ dokploy-network (both app and api accessible)
Example (App + API on different subdomains):
services: app: image: myapp-web:1.0.0 depends_on: postgres: condition: service_healthy networks: - app-net - dokploy-network labels: - "traefik.http.routers.app.rule=Host(`${DOMAIN}`)" # ... api: image: myapp-api:1.0.0 depends_on: postgres: condition: service_healthy networks: - app-net - dokploy-network labels: - "traefik.http.routers.api.rule=Host(`api.${DOMAIN}`)" # ... postgres: image: postgres:16-alpine networks: - app-net # Shared by both app and api
Dependency Conditions Reference
| Condition | When to Use | Example |
|---|---|---|
| Database, cache, required services | PostgreSQL, MongoDB, Redis |
| Helper services, on-demand converters | Gotenberg, Tika, sidecars |
| Init containers, migrations | DB migrations, setup scripts |
Complete Examples
Example 1: 2-Service (Forgejo + PostgreSQL)
services: forgejo: image: codeberg.org/forgejo/forgejo:9 restart: always depends_on: postgres: condition: service_healthy volumes: - forgejo-data:/data environment: FORGEJO__database__DB_TYPE: postgres FORGEJO__database__HOST: postgres:5432 FORGEJO__database__NAME: ${POSTGRES_DB:-forgejo} FORGEJO__database__USER: ${POSTGRES_USER:-forgejo} FORGEJO__database__PASSWD: ${POSTGRES_PASSWORD:?Set database password} networks: - forgejo-net - dokploy-network labels: - "traefik.enable=true" - "traefik.http.routers.forgejo.rule=Host(`${FORGEJO_DOMAIN}`)" - "traefik.http.routers.forgejo.entrypoints=websecure" - "traefik.http.routers.forgejo.tls.certresolver=letsencrypt" - "traefik.http.services.forgejo.loadbalancer.server.port=3000" - "traefik.docker.network=dokploy-network" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"] interval: 30s timeout: 10s retries: 3 start_period: 30s postgres: image: postgres:16-alpine restart: always volumes: - postgres-data:/var/lib/postgresql/data environment: POSTGRES_DB: ${POSTGRES_DB:-forgejo} POSTGRES_USER: ${POSTGRES_USER:-forgejo} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set database password} networks: - forgejo-net healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-forgejo} -d ${POSTGRES_DB:-forgejo}"] interval: 30s timeout: 10s retries: 3 start_period: 30s volumes: forgejo-data: driver: local postgres-data: driver: local networks: forgejo-net: driver: bridge dokploy-network: external: true
Example 2: 5-Service Complex Stack (Paperless-ngx)
services: paperless: image: ghcr.io/paperless-ngx/paperless-ngx:2.13 restart: always depends_on: postgres: condition: service_healthy redis: condition: service_healthy gotenberg: condition: service_started tika: condition: service_started volumes: - paperless-data:/usr/src/paperless/data - paperless-media:/usr/src/paperless/media - paperless-export:/usr/src/paperless/export - paperless-consume:/usr/src/paperless/consume environment: PAPERLESS_REDIS: redis://redis:6379 PAPERLESS_DBHOST: postgres PAPERLESS_DBNAME: ${POSTGRES_DB:-paperless} PAPERLESS_DBUSER: ${POSTGRES_USER:-paperless} PAPERLESS_DBPASS: ${POSTGRES_PASSWORD:?Set database password} PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY:?Set secret key} PAPERLESS_URL: https://${PAPERLESS_DOMAIN} PAPERLESS_TIKA_ENABLED: 1 PAPERLESS_TIKA_ENDPOINT: http://tika:9998 PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000 networks: - paperless-net - dokploy-network labels: - "traefik.enable=true" - "traefik.http.routers.paperless.rule=Host(`${PAPERLESS_DOMAIN}`)" - "traefik.http.routers.paperless.entrypoints=websecure" - "traefik.http.routers.paperless.tls.certresolver=letsencrypt" - "traefik.http.services.paperless.loadbalancer.server.port=8000" - "traefik.docker.network=dokploy-network" healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000"] interval: 30s timeout: 10s retries: 3 start_period: 60s postgres: image: postgres:16-alpine restart: always volumes: - postgres-data:/var/lib/postgresql/data environment: POSTGRES_DB: ${POSTGRES_DB:-paperless} POSTGRES_USER: ${POSTGRES_USER:-paperless} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set database password} networks: - paperless-net healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-paperless} -d ${POSTGRES_DB:-paperless}"] interval: 30s timeout: 10s retries: 3 start_period: 30s redis: image: redis:7-alpine restart: always volumes: - redis-data:/data networks: - paperless-net healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 30s timeout: 10s retries: 3 start_period: 10s gotenberg: image: gotenberg/gotenberg:8 restart: always networks: - paperless-net command: - "gotenberg" - "--chromium-disable-javascript=true" - "--chromium-allow-list=file:///tmp/.*" tika: image: apache/tika:2.9.1.0 restart: always networks: - paperless-net volumes: paperless-data: driver: local paperless-media: driver: local paperless-export: driver: local paperless-consume: driver: local postgres-data: driver: local redis-data: driver: local networks: paperless-net: driver: bridge dokploy-network: external: true
Service Communication Patterns
Internal Service URLs
| Service | Internal URL Format |
|---|---|
| PostgreSQL | |
| MongoDB | |
| Redis | |
| MySQL | |
| HTTP APIs | |
Environment Variable Patterns
# Database connections DATABASE_URL: postgresql://${DB_USER}:${DB_PASS}@postgres:5432/${DB_NAME} MONGO_URL: mongodb://mongodb:27017/${MONGO_DB} REDIS_URL: redis://redis:6379 # Internal HTTP services TIKA_ENDPOINT: http://tika:9998 GOTENBERG_ENDPOINT: http://gotenberg:3000 API_ENDPOINT: http://api:8080
Quality Standards
Mandatory Requirements
- All service dependencies explicitly declared
-
for required startup dependenciesservice_healthy -
for on-demand helpersservice_started - Databases on internal network only
- Web services on both networks
- Service names match across depends_on and environment URLs
Dependency Validation
- Each
service must existdepends_on - Health checks required for
dependenciesservice_healthy - Connection strings use service names, not IPs
Common Pitfalls
Pitfall 1: Circular dependencies
Issue: Service A depends on B, B depends on A Solution: Redesign architecture, use async communication
Pitfall 2: Missing health checks for dependencies
Issue:
service_healthy fails without health check
Solution: Add health check to dependency service
Pitfall 3: Wrong service name in URL
Issue: Connection refused errors Solution: Use exact docker-compose service name in URLs
Pitfall 4: Database exposed externally
Issue: Security vulnerability Solution: Remove dokploy-network from database services
Integration
Skills-First Approach (v2.0+)
This skill is part of the skills-first architecture - loaded during the Architecture phase to design complex service dependencies before generation begins.
Related Skills
: Base structure implementationdokploy-compose-structure
: Health check configurationdokploy-health-patterns
: Connection strings and service URLsdokploy-environment-config
Invoked By
command: Phase 2 (Architecture) - Loaded when 2+ services detected/dokploy-create
Order in Workflow (Progressive Loading)
- Phase 1: Discovery (research, no skills)
- This skill: Design service architecture (Phase 2)
- Phase 3: Generation skills (compose-structure → ... → template-toml)
- Phase 4: Validation skills
See:
.claude/commands/dokploy-create.md for full workflow