Your Dokploy setup works. Now make it bulletproof. This guide covers authentication with SSO, zero-trust access with Cloudflare Tunnel, secrets management, and a disaster recovery playbook you'll be grateful to have.
SSO with Authentik
Authentik is a self-hosted identity provider that adds single sign-on to all your applications — including Dokploy itself.
Why SSO?
- One login for Dokploy, Grafana, and all your apps
- Centralized user management — disable one account, lose access everywhere
- 2FA enforcement across all services
- Audit logs for who accessed what, when
Deploy Authentik
Create a new Docker Compose service in Dokploy
version: '3.8'
services:
authentik-db:
image: postgres:16-alpine
container_name: authentik-db
environment:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: ${AUTHENTIK_DB_PASSWORD}
volumes:
- authentik_db:/var/lib/postgresql/data
restart: unless-stopped
authentik-redis:
image: redis:alpine
container_name: authentik-redis
command: --save 60 1 --loglevel warning
volumes:
- authentik_redis:/data
restart: unless-stopped
authentik-server:
image: ghcr.io/goauthentik/server:latest
container_name: authentik-server
command: server
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
volumes:
- authentik_media:/media
- authentik_templates:/templates
ports:
- "9000:9000"
- "9443:9443"
depends_on:
- authentik-db
- authentik-redis
restart: unless-stopped
authentik-worker:
image: ghcr.io/goauthentik/server:latest
container_name: authentik-worker
command: worker
environment:
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_REDIS__HOST: authentik-redis
AUTHENTIK_POSTGRESQL__HOST: authentik-db
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
volumes:
- authentik_media:/media
- authentik_templates:/templates
depends_on:
- authentik-db
- authentik-redis
restart: unless-stopped
volumes:
authentik_db:
authentik_redis:
authentik_media:
authentik_templates:Environment Variables
AUTHENTIK_SECRET_KEY=generate-a-long-random-string-here
AUTHENTIK_DB_PASSWORD=another-secure-passwordGenerate a secret key:
openssl rand -base64 36Initial Setup
- Visit
http://your-server-ip:9000/if/flow/initial-setup/ - Create your admin account
- Set up 2FA (strongly recommended)
Protect Dokploy with Authentik
In Authentik:
- Go to Applications → Providers → Create
- Select OAuth2/OpenID Provider
- Configure:
- Name:
Dokploy - Authorization flow:
default-provider-authorization-implicit-consent - Client ID: (auto-generated, copy this)
- Client Secret: (click to reveal, copy this)
- Redirect URIs:
https://dokploy.yourdomain.com/api/auth/callback/authentik
- Name:
- Go to Applications → Applications → Create
- Configure: Name:
Dokploy, Slug:dokploy, Provider: Select the provider you just created
In Dokploy:
- Go to Settings → Authentication
- Add OAuth provider with:
- Provider: Custom OAuth
- Client ID: (from Authentik)
- Client Secret: (from Authentik)
- Authorization URL:
https://auth.yourdomain.com/application/o/authorize/ - Token URL:
https://auth.yourdomain.com/application/o/token/ - Userinfo URL:
https://auth.yourdomain.com/application/o/userinfo/
Protect Grafana with Authentik
Create another OAuth2 provider and application for Grafana in Authentik, then set these environment variables:
GF_AUTH_GENERIC_OAUTH_ENABLED=true
GF_AUTH_GENERIC_OAUTH_NAME=Authentik
GF_AUTH_GENERIC_OAUTH_CLIENT_ID=your-client-id
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET=your-client-secret
GF_AUTH_GENERIC_OAUTH_SCOPES=openid profile email
GF_AUTH_GENERIC_OAUTH_AUTH_URL=https://auth.yourdomain.com/application/o/authorize/
GF_AUTH_GENERIC_OAUTH_TOKEN_URL=https://auth.yourdomain.com/application/o/token/
GF_AUTH_GENERIC_OAUTH_API_URL=https://auth.yourdomain.com/application/o/userinfo/
GF_AUTH_GENERIC_OAUTH_ROLE_ATTRIBUTE_PATH=contains(groups[*], 'Grafana Admins') && 'Admin' || 'Viewer'Forward Auth for Any Application
For apps that don't support OAuth natively, use Authentik's forward auth with Traefik
labels:
- "traefik.http.routers.myapp.middlewares=authentik@docker"
- "traefik.http.middlewares.authentik.forwardauth.address=http://authentik-server:9000/outpost.goauthentik.io/auth/traefik"
- "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=X-authentik-username,X-authentik-groups,X-authentik-email"This puts Authentik login in front of any application — even static sites.
Zero-Trust Access with Cloudflare Tunnel
Cloudflare Tunnel connects your server to Cloudflare's network without opening any inbound ports. No public IP exposure, no firewall holes.
Why Cloudflare Tunnel?
- No open ports — Your server is invisible to port scanners
- DDoS protection — Cloudflare absorbs attacks
- Free SSL — Automatic certificates
- Access policies — Require authentication before reaching your apps
Install cloudflared
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflaredAuthenticate and Create Tunnel
cloudflared tunnel loginThis opens a browser to authenticate with Cloudflare. Select your domain.
cloudflared tunnel create dokploy-tunnelNote the tunnel ID and credentials file path.
Configure the Tunnel
tunnel: your-tunnel-id
credentials-file: /root/.cloudflared/your-tunnel-id.json
ingress:
# Dokploy dashboard
- hostname: dokploy.yourdomain.com
service: http://localhost:3000
# Your applications
- hostname: app.yourdomain.com
service: http://localhost:80
# Grafana
- hostname: grafana.yourdomain.com
service: http://localhost:3001
# Catch-all (required)
- service: http_status:404Create DNS Records and Run as Service
cloudflared tunnel route dns dokploy-tunnel dokploy.yourdomain.com
cloudflared tunnel route dns dokploy-tunnel app.yourdomain.com
cloudflared tunnel route dns dokploy-tunnel grafana.yourdomain.comsudo cloudflared service install
sudo systemctl start cloudflared
sudo systemctl enable cloudflaredLock Down the Firewall
Now that traffic comes through Cloudflare, close the public ports:
# Remove public access
sudo ufw delete allow 80/tcp
sudo ufw delete allow 443/tcp
sudo ufw delete allow 3000/tcp
sudo ufw delete allow 3001/tcp
# Keep SSH (or tunnel that too)
sudo ufw statusYour services are now only accessible through Cloudflare Tunnel.
Cloudflare Access (Optional)
Add another authentication layer through Cloudflare's dashboard:
- Go to Cloudflare Zero Trust → Access → Applications
- Add an application
- Set policies (e.g., require email ending in
@yourcompany.com) - Users must authenticate with Cloudflare before reaching your app
This stacks with Authentik — Cloudflare Access for network-level auth, Authentik for application-level auth.
Secrets Management with Infisical
Hardcoded secrets in environment variables work, but don't scale. Infisical centralizes secrets with versioning, access control, and audit logs.
Deploy Infisical
version: '3.8'
services:
infisical-db:
image: postgres:16-alpine
container_name: infisical-db
environment:
POSTGRES_DB: infisical
POSTGRES_USER: infisical
POSTGRES_PASSWORD: ${INFISICAL_DB_PASSWORD}
volumes:
- infisical_db:/var/lib/postgresql/data
restart: unless-stopped
infisical-redis:
image: redis:alpine
container_name: infisical-redis
volumes:
- infisical_redis:/data
restart: unless-stopped
infisical:
image: infisical/infisical:latest
container_name: infisical
environment:
ENCRYPTION_KEY: ${INFISICAL_ENCRYPTION_KEY}
JWT_SIGNUP_SECRET: ${INFISICAL_JWT_SIGNUP_SECRET}
JWT_REFRESH_SECRET: ${INFISICAL_JWT_REFRESH_SECRET}
JWT_AUTH_SECRET: ${INFISICAL_JWT_AUTH_SECRET}
SITE_URL: https://secrets.yourdomain.com
DB_CONNECTION_URI: postgresql://infisical:${INFISICAL_DB_PASSWORD}@infisical-db:5432/infisical
REDIS_URL: redis://infisical-redis:6379
ports:
- "8070:8080"
depends_on:
- infisical-db
- infisical-redis
restart: unless-stopped
volumes:
infisical_db:
infisical_redis:Generate Secrets
# Encryption key (32 bytes, hex encoded)
openssl rand -hex 32
# JWT secrets
openssl rand -base64 32
openssl rand -base64 32
openssl rand -base64 32Initial Setup
- Visit
http://your-server-ip:8070 - Create your organization and admin account
- Create a project for your application
- Add environment (e.g.,
production,staging) - Add your secrets:
DATABASE_URL,REDIS_URL,API_KEY, etc.
Option 1: Infisical CLI in Dockerfile
# Install Infisical CLI
RUN curl -fsSL https://raw.githubusercontent.com/Infisical/infisical/main/scripts/install.sh | sh
# Run with secrets injected
CMD ["infisical", "run", "--", "node", "server.js"]Set these environment variables in Dokploy:
INFISICAL_TOKEN=your-service-token
INFISICAL_PROJECT_ID=your-project-id
INFISICAL_ENVIRONMENT=productionAccess Control
Create service tokens with minimal permissions:
- Go to Project Settings → Service Tokens
- Create token with:
- Environment:
production - Permissions: Read only
- Expiry: 90 days
- Environment:
Different tokens for different apps means compromising one doesn't expose everything.
Disaster Recovery Playbook
When things go wrong, you need a plan — not a panic.
What Could Go Wrong
| Scenario | Impact | Recovery Time |
|---|---|---|
| Container crash | Single app down | Minutes (auto-restart) |
| Server reboot | All apps down briefly | 5-10 minutes |
| Disk failure | Data loss | Hours (restore from backup) |
| Server compromise | Full breach | Hours to days |
| Datacenter outage | Everything down | Hours (failover to new server) |
Prevention Checklist
- Automated daily backups (Part 3)
- Offsite backup sync (Restic to S3)
- Monitoring and alerting (Part 5)
- Tested restore procedure
- Documented server configuration
- Multi-factor authentication everywhere
- Firewall configured (UFW)
- Regular security updates
Server Configuration as Code
Document everything needed to rebuild your server from scratch
# Server Configuration
## Base Setup
- Ubuntu 24.04 LTS
- RamNode Standard VPS, 4GB RAM, NL datacenter
- Domain: yourdomain.com
## Installed Software
- Docker (via get.docker.com)
- Dokploy (via install script)
- cloudflared (Cloudflare Tunnel)
- UFW firewall
## Firewall Rules
- 22/tcp (SSH)
- All other traffic via Cloudflare Tunnel
## DNS Records
- dokploy.yourdomain.com → Cloudflare Tunnel
- app.yourdomain.com → Cloudflare Tunnel
- auth.yourdomain.com → Cloudflare Tunnel
## Secrets Location
- Infisical: https://secrets.yourdomain.com
- Backup encryption key: In password manager
## Backup Schedule
- Daily at 3 AM UTC
- Retained 14 days locally
- Synced to S3: bucket-name
- Retention: 7 daily, 4 weekly, 6 monthlyFull Server Rebuild
- Provision new VPS — Same specs as documented, same datacenter if possible
- Base setup:
apt update && apt upgrade -y apt install -y curl git ufw curl -fsSL https://get.docker.com | sh - Install Dokploy:
curl -sSL https://dokploy.com/install.sh | sh - Restore data:
# Install restic apt install restic # Restore from S3 export AWS_ACCESS_KEY_ID="your-key" export AWS_SECRET_ACCESS_KEY="your-secret" restic -r s3:s3.region.amazonaws.com/your-bucket restore latest --target / - Restore databases:
gunzip -c backup.sql.gz | docker exec -i <db-container> psql -U user dbname - Update DNS / Cloudflare Tunnel:
cloudflared tunnel login cloudflared tunnel route dns <tunnel-name> <hostname> - Verify everything works — Check all applications respond, verify database connections, test authentication, confirm monitoring is receiving data
Incident Response Template
## Incident: [Brief description]
**Date:** YYYY-MM-DD HH:MM UTC
**Duration:** X hours Y minutes
**Severity:** Critical / High / Medium / Low
### Timeline
- HH:MM - Alert received
- HH:MM - Investigation started
- HH:MM - Root cause identified
- HH:MM - Fix deployed
- HH:MM - Service restored
### Root Cause
[What actually broke and why]
### Resolution
[What was done to fix it]
### Prevention
[What we'll do to prevent this happening again]
### Action Items
- [ ] Task 1
- [ ] Task 2Security Hardening Checklist
Server Level
- SSH key authentication only
- Non-root user for daily operations
- UFW firewall enabled
- Automatic security updates
- Fail2ban installed
Docker Level
- Keep Docker updated
- Use official/trusted images
- Run as non-root users
- Set resource limits
- Scan images for vulnerabilities
Application Level
- All traffic over HTTPS
- Strong passwords / SSO enforced
- 2FA enabled for admin accounts
- Secrets in Infisical, not env vars
- Regular dependency updates
Network Level
- Cloudflare Tunnel (no exposed ports)
- Cloudflare Access policies
- Internal services not public
- Database ports closed
Essential Security Commands
# Disable password authentication
sudo sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# Enable automatic updates
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# Install fail2ban
sudo apt install fail2ban
sudo systemctl enable fail2ban
# Scan images with Trivy
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy image myapp:latestMaintenance Schedule
Daily (Automated)
- Backups run at 3 AM
- Backup sync to offsite storage
- Monitoring checks every minute
Weekly
- Review monitoring dashboards
- Check backup completion logs
- Review security alerts
Monthly
- Apply system updates
- Rotate service tokens
- Test backup restoration
- Review access logs
- Update documentation
Quarterly
- Full disaster recovery test
- Security audit
- Dependency updates
- Performance review
- Cost optimization review
Series Complete!
You've built a production-grade deployment platform:
- Part 1 — Dokploy installed, first app deployed
- Part 2 — Production Dockerfiles for real frameworks
- Part 3 — Databases with automated backups
- Part 4 — CI/CD pipelines with automatic deploys
- Part 5 — Full observability stack
- Part 6 — Hardened for production
This setup rivals managed platforms like Vercel, Render, or Railway — at a fraction of the cost, with full control over your infrastructure.
