| name | dokploy-multi-tenant |
| description | Multi-tenancy patterns for Dokploy templates with network isolation: separate docker networks per tenant, shared infrastructure, and tenant-specific configuration. |
| version | 1.0.0 |
| author | Home Lab Infrastructure Team |
Dokploy Multi-Tenant Patterns
When to Use This Skill
- When deploying multiple instances of the same template
- When different clients/projects need isolated deployments
- When user asks about "multi-tenant" or "tenant isolation"
- When planning organization-wide deployments
When NOT to Use This Skill
- For single-instance deployments
- For application-level multi-tenancy (built into app)
Prerequisites
- Understanding of Docker networking
- Clear tenant identification strategy
- Knowledge of shared vs dedicated resources
Multi-Tenancy Strategy: Network Isolation
This implementation uses separate Docker networks per tenant with shared infrastructure:
┌────────────────────────────────────────────────────────────────┐
│ Shared Infrastructure │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Traefik │ │ Monitoring │ │ Logging │ │
│ │ (dokploy) │ │ (shared) │ │ (shared) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ │ dokploy-network │
└─────────┼──────────────────────────────────────────────────────┘
│
┌─────┴─────┬─────────────┬─────────────┐
│ │ │ │
┌───┴───┐ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐
│Tenant1│ │Tenant2│ │Tenant3│ │TenantN│
│ net │ │ net │ │ net │ │ net │
│ │ │ │ │ │ │ │
│ ┌───┐ │ │ ┌───┐ │ │ ┌───┐ │ │ ┌───┐ │
│ │App│ │ │ │App│ │ │ │App│ │ │ │App│ │
│ │DB │ │ │ │DB │ │ │ │DB │ │ │ │DB │ │
│ └───┘ │ │ └───┘ │ │ └───┘ │ │ └───┘ │
└───────┘ └───────┘ └───────┘ └───────┘
Characteristics:
- Each tenant has isolated Docker network
- Tenant services cannot communicate with other tenants
- All tenants share Traefik via dokploy-network
- Each tenant has dedicated domain/subdomain
Core Patterns
Pattern 1: Tenant Network Naming
networks:
${TENANT_ID}-net:
driver: bridge
dokploy-network:
external: true
Naming Convention:
- Format:
${tenant_id}-${app_name}-net or ${tenant_id}-net
- Examples:
acme-corp-net, client-a-net, project-123-net
Pattern 2: Tenant Domain Strategy
Subdomain per Tenant:
labels:
- "traefik.http.routers.${TENANT_ID}.rule=Host(`${TENANT_ID}.${BASE_DOMAIN}`)"
Custom Domain per Tenant:
labels:
- "traefik.http.routers.${TENANT_ID}.rule=Host(`${TENANT_DOMAIN}`)"
Path-Based Tenancy (Not Recommended):
labels:
- "traefik.http.routers.${TENANT_ID}.rule=Host(`${DOMAIN}`) && PathPrefix(`/${TENANT_ID}`)"
Pattern 3: Tenant-Specific Environment
services:
app:
environment:
TENANT_ID: ${TENANT_ID:?Set tenant identifier}
TENANT_NAME: ${TENANT_NAME:?Set tenant name}
TENANT_DOMAIN: ${TENANT_DOMAIN:?Set tenant domain}
Pattern 4: Isolated Volumes per Tenant
volumes:
${TENANT_ID}-app-data:
driver: local
${TENANT_ID}-postgres-data:
driver: local
Naming Convention:
- Format:
${tenant_id}-${service}-${type}
- Examples:
acme-corp-postgres-data, client-a-app-uploads
Complete Example: Multi-Tenant Template
docker-compose.yml
services:
app:
image: myapp:1.0.0
restart: always
depends_on:
postgres:
condition: service_healthy
environment:
TENANT_ID: ${TENANT_ID:?Set tenant identifier}
TENANT_NAME: ${TENANT_NAME:-${TENANT_ID}}
APP_DOMAIN: ${TENANT_DOMAIN:?Set tenant domain}
APP_URL: https://${TENANT_DOMAIN}
DATABASE_URL: postgresql://${DB_USER:-app}:${DB_PASS}@postgres:5432/${DB_NAME:-app}
SECRET_KEY: ${SECRET_KEY:?Set secret key}
networks:
- ${TENANT_ID}-net
- dokploy-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.${TENANT_ID}-app.rule=Host(`${TENANT_DOMAIN}`)"
- "traefik.http.routers.${TENANT_ID}-app.entrypoints=websecure"
- "traefik.http.routers.${TENANT_ID}-app.tls.certresolver=letsencrypt"
- "traefik.http.services.${TENANT_ID}-app.loadbalancer.server.port=8080"
- "traefik.docker.network=dokploy-network"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
postgres:
image: postgres:16-alpine
restart: always
volumes:
- ${TENANT_ID}-postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_DB: ${DB_NAME:-app}
POSTGRES_USER: ${DB_USER:-app}
POSTGRES_PASSWORD: ${DB_PASS:?Set database password}
networks:
- ${TENANT_ID}-net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-app} -d ${DB_NAME:-app}"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
volumes:
${TENANT_ID}-postgres-data:
driver: local
networks:
${TENANT_ID}-net:
driver: bridge
dokploy-network:
external: true
template.toml
[variables]
tenant_id = "${username}"
tenant_domain = "${domain}"
db_password = "${password:32}"
secret_key = "${base64:64}"
[[config.domains]]
serviceName = "app"
port = 8080
host = "${tenant_domain}"
[config.env]
TENANT_ID = "${tenant_id}"
TENANT_NAME = "${tenant_id}"
TENANT_DOMAIN = "${tenant_domain}"
DB_NAME = "app"
DB_USER = "app"
DB_PASS = "${db_password}"
POSTGRES_DB = "app"
POSTGRES_USER = "app"
POSTGRES_PASSWORD = "${db_password}"
SECRET_KEY = "${secret_key}"
Deployment Strategies
Strategy 1: Subdomain Tenants
Each tenant gets tenant.example.com:
[config.env]
TENANT_ID = "acme"
TENANT_DOMAIN = "acme.example.com"
DNS Setup:
*.example.com → Your Dokploy server
Strategy 2: Custom Domain Tenants
Each tenant brings their own domain:
[config.env]
TENANT_ID = "acme"
TENANT_DOMAIN = "app.acmecorp.com"
DNS Setup (per tenant):
app.acmecorp.com CNAME your-dokploy-server.example.com
Strategy 3: Project-Based Tenants
Each project is a tenant:
[config.env]
TENANT_ID = "project-123"
TENANT_DOMAIN = "project-123.projects.example.com"
Resource Sharing vs Isolation
Shared Resources (Cost Effective)
| Resource | Shared | Notes |
|---|
| Traefik | Yes | Single instance routes all tenants |
| Monitoring | Yes | Central Prometheus/Grafana |
| Logging | Yes | Central logging stack |
| SSL Certs | Yes | LetsEncrypt for all |
Isolated Resources (Security/Performance)
| Resource | Isolated | Notes |
|---|
| Docker Network | Yes | Tenant-specific network |
| Database | Yes | Dedicated database per tenant |
| Volumes | Yes | Tenant-specific volumes |
| CPU/Memory | Optional | Use resource limits |
Quality Standards
Mandatory Requirements
Naming Conventions
- Network:
${tenant_id}-net or ${tenant_id}-${app}-net
- Volume:
${tenant_id}-${service}-data
- Router:
${tenant_id}-${service}
Common Pitfalls
Pitfall 1: Duplicate router names
Issue: Two tenants with same router name conflict
Solution: Include tenant ID in all router names
Pitfall 2: Shared volumes
Issue: Tenants overwrite each other's data
Solution: Include tenant ID in volume names
Pitfall 3: Cross-tenant network access
Issue: Tenants can access each other's services
Solution: Each tenant on separate network
Pitfall 4: Hardcoded tenant ID
Issue: Can't deploy multiple instances
Solution: Use ${TENANT_ID} variable everywhere
Migration: Single to Multi-Tenant
To convert existing single-tenant template:
- Add TENANT_ID variable:
[variables]
tenant_id = "${username}"
- Update network name:
networks:
${TENANT_ID}-net:
driver: bridge
- Update volume names:
volumes:
${TENANT_ID}-postgres-data:
driver: local
- Update router names:
labels:
- "traefik.http.routers.${TENANT_ID}-app.rule=..."
- Add tenant domain:
environment:
TENANT_DOMAIN: ${TENANT_DOMAIN:?Set tenant domain}
Integration
Skills-First Approach (v2.0+)
This skill is part of the skills-first architecture - an advanced pattern loaded when multi-tenant architecture is specifically required (not part of standard workflow).
Related Skills
dokploy-compose-structure: Network patterns
dokploy-traefik-routing: Domain routing
dokploy-environment-config: Tenant variables
dokploy-multi-service: Multi-service dependency patterns
Invoked By
- Special case: When user explicitly requests multi-tenant architecture
- Not part of standard
/dokploy-create workflow
- Can be manually invoked for advanced use cases
Usage Notes
- This is an advanced pattern skill for specific multi-tenant scenarios
- Standard single-tenant templates use the regular
/dokploy-create workflow
- Use this skill when building SaaS platforms or multi-tenant applications
See: /dokploy-create for standard workflow, this skill for multi-tenant variants