Skip to content
Go back

Dokploy Deployment Automation: Self-Hosted DevOps Platform

Dokploy Deployment Automation: Self-Hosted DevOps Platform

Dokploy is a powerful self-hosted deployment platform that simplifies application deployment and management. This comprehensive guide covers setting up Dokploy for automated deployments, Docker orchestration, and complete DevOps workflows.

Why Choose Dokploy?

Step 1: Dokploy Installation and Setup

Install Dokploy on your VPS or dedicated server:

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER

# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Install Dokploy
curl -sSL https://dokploy.com/install.sh | sh

# Start Dokploy services
dokploy start

Step 2: Initial Configuration

Configure Dokploy for your environment:

# Access Dokploy configuration
dokploy config

# Set up domain and SSL
dokploy domain add yourdomain.com
dokploy ssl enable yourdomain.com

# Configure Git integration
dokploy git setup

Create initial configuration file:

version: "1.0"

# Global configuration
global:
  domain: "dokploy.yourdomain.com"
  ssl:
    enabled: true
    email: "admin@yourdomain.com"
    provider: "letsencrypt"

  # Database configuration
  database:
    postgres:
      enabled: true
      version: "15"
      port: 5432
    mysql:
      enabled: false
    redis:
      enabled: true
      version: "7"
      port: 6379

  # Backup configuration
  backups:
    enabled: true
    schedule: "0 2 * * *" # Daily at 2 AM
    retention: 30 # days
    storage:
      type: "s3"
      bucket: "dokploy-backups"
      region: "us-east-1"

# Network configuration
networks:
  dokploy:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

# Default resource limits
resources:
  default:
    memory: "512m"
    cpu: "0.5"
  small:
    memory: "256m"
    cpu: "0.25"
  medium:
    memory: "1g"
    cpu: "1"
  large:
    memory: "2g"
    cpu: "2"dokploy.config.yml

Step 3: Application Template Configuration

Create reusable application templates:

name: "Node.js Application"
description: "Template for Node.js applications with PostgreSQL"
category: "web"

# Build configuration
build:
  context: "."
  dockerfile: "Dockerfile"
  args:
    NODE_ENV: "production"
  target: "production"

# Environment variables
environment:
  - name: "NODE_ENV"
    value: "production"
    required: true
  - name: "PORT"
    value: "3000"
    required: true
  - name: "DATABASE_URL"
    value: "postgresql://{{.postgres_user}}:{{.postgres_password}}@postgres:5432/{{.postgres_db}}"
    required: true
  - name: "REDIS_URL"
    value: "redis://redis:6379"
    required: false
  - name: "JWT_SECRET"
    value: "{{.jwt_secret}}"
    required: true
    secret: true

# Service configuration
services:
  app:
    build: .
    ports:
      - "3000:3000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://{{.postgres_user}}:{{.postgres_password}}@postgres:5432/{{.postgres_db}}
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET={{.jwt_secret}}
    volumes:
      - app_storage:/app/storage
      - app_logs:/app/logs
    labels:
      - "dokploy.enable=true"
      - "dokploy.http.routers.app.rule=Host(`{{.domain}}`)"
      - "dokploy.http.routers.app.tls=true"
      - "dokploy.http.routers.app.tls.certresolver=letsencrypt"
      - "dokploy.http.services.app.loadbalancer.server.port=3000"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: "{{.postgres_db}}"
      POSTGRES_USER: "{{.postgres_user}}"
      POSTGRES_PASSWORD: "{{.postgres_password}}"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    labels:
      - "dokploy.enable=false"
    restart: unless-stopped
    healthcheck:
      test:
        ["CMD-SHELL", "pg_isready -U {{.postgres_user}} -d {{.postgres_db}}"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    labels:
      - "dokploy.enable=false"
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

# Volume configuration
volumes:
  postgres_data:
  redis_data:
  app_storage:
  app_logs:

# Health checks
healthcheck:
  path: "/health"
  interval: 30
  timeout: 10
  retries: 3

# Deployment configuration
deployment:
  strategy: "rolling"
  max_unavailable: 1
  max_surge: 1

# Scaling configuration
scaling:
  enabled: true
  min_replicas: 1
  max_replicas: 5
  target_cpu: 70
  target_memory: 80templates/nodejs-app.yml

Step 4: Advanced Docker Compose Setup

Create a comprehensive Docker Compose configuration:

version: "3.8"

services:
  # Main application
  web-app:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
      args:
        NODE_ENV: production
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://app_user:${POSTGRES_PASSWORD}@postgres:5432/app_db
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
      - API_KEY=${API_KEY}
      - SMTP_HOST=${SMTP_HOST}
      - SMTP_PORT=${SMTP_PORT}
      - SMTP_USER=${SMTP_USER}
      - SMTP_PASS=${SMTP_PASS}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - app_storage:/app/storage
      - app_logs:/app/logs
      - app_uploads:/app/uploads
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.yourdomain.com`)"
      - "traefik.http.routers.app.tls=true"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"
      - "traefik.http.services.app.loadbalancer.server.port=3000"
      - "traefik.http.routers.app.middlewares=auth,ratelimit"
    networks:
      - dokploy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

  # Background worker
  worker:
    build:
      context: .
      dockerfile: Dockerfile.worker
      args:
        NODE_ENV: production
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://app_user:${POSTGRES_PASSWORD}@postgres:5432/app_db
      - REDIS_URL=redis://redis:6379
      - QUEUE_NAME=default
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - app_logs:/app/logs
    networks:
      - dokploy
    restart: unless-stopped
    deploy:
      replicas: 2

  # Database
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: app_db
      POSTGRES_USER: app_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_INITDB_ARGS: "--auth-host=scram-sha-256"
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - dokploy
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app_user -d app_db"]
      interval: 10s
      timeout: 5s
      retries: 5
    command:
      [
        "postgres",
        "-c",
        "shared_preload_libraries=pg_stat_statements",
        "-c",
        "pg_stat_statements.track=all",
        "-c",
        "max_connections=200",
        "-c",
        "shared_buffers=256MB",
        "-c",
        "effective_cache_size=1GB",
        "-c",
        "work_mem=4MB",
        "-c",
        "maintenance_work_mem=64MB",
      ]

  # Redis cache
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
      - ./redis.conf:/usr/local/etc/redis/redis.conf
    command: redis-server /usr/local/etc/redis/redis.conf
    networks:
      - dokploy
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3

  # Traefik reverse proxy
  traefik:
    image: traefik:v3.0
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--global.checkNewVersion=false"
      - "--log.level=INFO"
      - "--accesslog=true"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik_letsencrypt:/letsencrypt
      - traefik_logs:/var/log/traefik
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
      - "traefik.http.routers.traefik.tls=true"
      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.routers.traefik.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$10$$..."
      - "traefik.http.middlewares.ratelimit.ratelimit.burst=100"
      - "traefik.http.middlewares.ratelimit.ratelimit.average=50"
    networks:
      - dokploy
    restart: unless-stopped

  # Monitoring with Prometheus
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
      - "--web.console.libraries=/etc/prometheus/console_libraries"
      - "--web.console.templates=/etc/prometheus/consoles"
      - "--storage.tsdb.retention.time=30d"
      - "--web.enable-lifecycle"
    networks:
      - dokploy
    restart: unless-stopped

  # Grafana for visualization
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3001:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana_data:/var/lib/grafana
      - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.rule=Host(`grafana.yourdomain.com`)"
      - "traefik.http.routers.grafana.tls=true"
      - "traefik.http.routers.grafana.tls.certresolver=letsencrypt"
    networks:
      - dokploy
    restart: unless-stopped

  # Log aggregation with Loki
  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    volumes:
      - ./monitoring/loki.yml:/etc/loki/local-config.yaml
      - loki_data:/loki
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - dokploy
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:
  app_storage:
  app_logs:
  app_uploads:
  traefik_letsencrypt:
  traefik_logs:
  prometheus_data:
  grafana_data:
  loki_data:

networks:
  dokploy:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16docker-compose.production.yml

Step 5: CI/CD Pipeline Integration

Create GitHub Actions workflow for Dokploy deployment:

name: Deploy to Dokploy

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  test:
    runs-on: ubuntu-latest

    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "18"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Run tests
        run: npm test
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
          REDIS_URL: redis://localhost:6379

      - name: Build application
        run: npm run build

  build-and-push:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=sha,prefix=main-
            type=raw,value=latest

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
      - name: Deploy to Dokploy
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.DOKPLOY_HOST }}
          username: ${{ secrets.DOKPLOY_USER }}
          key: ${{ secrets.DOKPLOY_SSH_KEY }}
          script: |
            # Navigate to application directory
            cd /opt/dokploy/apps/my-app

            # Pull latest changes
            git pull origin main

            # Update environment variables
            echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}" > .env.production
            echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> .env.production
            echo "API_KEY=${{ secrets.API_KEY }}" >> .env.production

            # Deploy using Dokploy CLI
            dokploy app deploy my-app --env production

            # Wait for deployment to complete
            dokploy app status my-app --wait

            # Run database migrations if needed
            dokploy app exec my-app "npm run migrate"

            # Health check
            sleep 30
            if ! curl -f https://app.yourdomain.com/health; then
              echo "Health check failed, rolling back"
              dokploy app rollback my-app
              exit 1
            fi

      - name: Notify deployment status
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          channel: "#deployments"
          webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
        if: always().github/workflows/deploy.yml

Step 6: Monitoring and Alerting Setup

Configure monitoring with Prometheus and Grafana:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "rules/*.yml"

alerting:
  alertmanagers:
    - static_configs:
        - targets:
            - alertmanager:9093

scrape_configs:
  - job_name: "prometheus"
    static_configs:
      - targets: ["localhost:9090"]

  - job_name: "node-exporter"
    static_configs:
      - targets: ["node-exporter:9100"]

  - job_name: "docker"
    static_configs:
      - targets: ["dockerd-exporter:9323"]

  - job_name: "app"
    static_configs:
      - targets: ["web-app:3000"]
    metrics_path: "/metrics"
    scrape_interval: 5s

  - job_name: "postgres"
    static_configs:
      - targets: ["postgres-exporter:9187"]

  - job_name: "redis"
    static_configs:
      - targets: ["redis-exporter:9121"]

  - job_name: "traefik"
    static_configs:
      - targets: ["traefik:8080"]
    metrics_path: "/metrics"monitoring/prometheus.yml

Step 7: Backup and Recovery Automation

Create automated backup scripts:

#!/bin/bash

# Dokploy backup script
BACKUP_DIR="/opt/dokploy-backups"
S3_BUCKET="${BACKUP_S3_BUCKET}"
DATE=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/dokploy-backup.log"

# Function to log messages
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Backup application data
backup_application() {
    local app_name="$1"
    log "Backing up application: $app_name"

    # Create application backup
    dokploy backup create "$app_name" --output "$BACKUP_DIR/app_${app_name}_${DATE}.tar.gz"

    if [ $? -eq 0 ]; then
        log "✅ Application backup completed for $app_name"
    else
        log "❌ Application backup failed for $app_name"
    fi
}

# Backup database
backup_database() {
    log "Backing up PostgreSQL database"

    # Create database dump
    docker exec dokploy_postgres_1 pg_dump -U app_user app_db > "$BACKUP_DIR/database_${DATE}.sql"

    if [ $? -eq 0 ]; then
        log "✅ Database backup completed"
    else
        log "❌ Database backup failed"
    fi
}

# Backup Redis data
backup_redis() {
    log "Backing up Redis data"

    # Create Redis dump
    docker exec dokploy_redis_1 redis-cli BGSAVE
    docker cp dokploy_redis_1:/data/dump.rdb "$BACKUP_DIR/redis_${DATE}.rdb"

    if [ $? -eq 0 ]; then
        log "✅ Redis backup completed"
    else
        log "❌ Redis backup failed"
    fi
}

# Backup Docker volumes
backup_volumes() {
    log "Backing up Docker volumes"

    # Create tar archive of all volumes
    docker run --rm \
        -v dokploy_postgres_data:/source/postgres:ro \
        -v dokploy_app_storage:/source/app_storage:ro \
        -v dokploy_app_logs:/source/app_logs:ro \
        -v "$BACKUP_DIR":/backup \
        alpine \
        tar czf "/backup/volumes_${DATE}.tar.gz" -C /source .

    if [ $? -eq 0 ]; then
        log "✅ Volume backup completed"
    else
        log "❌ Volume backup failed"
    fi
}

# Upload to S3
upload_to_s3() {
    if [ -n "$S3_BUCKET" ]; then
        log "Uploading backups to S3"

        aws s3 sync "$BACKUP_DIR" "s3://$S3_BUCKET/dokploy-backups/" \
            --exclude "*" --include "*${DATE}*"

        if [ $? -eq 0 ]; then
            log "✅ S3 upload completed"
        else
            log "❌ S3 upload failed"
        fi
    fi
}

# Cleanup old backups
cleanup_old_backups() {
    log "Cleaning up old backups"

    # Keep backups for 30 days
    find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete
    find "$BACKUP_DIR" -name "*.sql" -mtime +30 -delete
    find "$BACKUP_DIR" -name "*.rdb" -mtime +30 -delete

    log "✅ Cleanup completed"
}

# Main backup function
main() {
    log "Starting Dokploy backup process"

    backup_application "my-app"
    backup_database
    backup_redis
    backup_volumes
    upload_to_s3
    cleanup_old_backups

    log "Backup process completed"
}

# Run backup
mainscripts/backup.sh

Step 8: Security and Optimization

Implement security best practices:

#!/bin/bash

# Dokploy security hardening script
log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}

# Configure firewall
setup_firewall() {
    log "Configuring firewall..."

    # Reset firewall
    ufw --force reset

    # Default policies
    ufw default deny incoming
    ufw default allow outgoing

    # SSH access
    ufw allow 22/tcp

    # HTTP/HTTPS
    ufw allow 80/tcp
    ufw allow 443/tcp

    # Dokploy dashboard
    ufw allow 8080/tcp

    # Rate limiting for SSH
    ufw limit 22/tcp

    # Enable firewall
    ufw --force enable

    log "✅ Firewall configured"
}

# Configure Docker security
secure_docker() {
    log "Securing Docker..."

    # Create Docker daemon configuration
    cat > /etc/docker/daemon.json << EOF
{
    "log-driver": "json-file",
    "log-opts": {
        "max-size": "10m",
        "max-file": "3"
    },
    "live-restore": true,
    "userland-proxy": false,
    "no-new-privileges": true,
    "seccomp-profile": "/etc/docker/seccomp.json"
}
EOF

    # Restart Docker
    systemctl restart docker

    log "✅ Docker secured"
}

# Configure system limits
configure_limits() {
    log "Configuring system limits..."

    cat > /etc/security/limits.d/dokploy.conf << EOF
# Dokploy security limits
* soft nofile 65536
* hard nofile 65536
* soft nproc 4096
* hard nproc 4096
dokploy soft nofile 65536
dokploy hard nofile 65536
EOF

    log "✅ System limits configured"
}

# Setup log rotation
setup_log_rotation() {
    log "Setting up log rotation..."

    cat > /etc/logrotate.d/dokploy << EOF
/opt/dokploy/logs/*.log {
    daily
    missingok
    rotate 30
    compress
    notifempty
    create 644 dokploy dokploy
    postrotate
        systemctl reload dokploy
    endscript
}
EOF

    log "✅ Log rotation configured"
}

# Main security function
main() {
    log "Starting Dokploy security hardening..."

    setup_firewall
    secure_docker
    configure_limits
    setup_log_rotation

    log "Security hardening completed"
}

mainscripts/security-hardening.sh

Best Practices Summary

  1. Use templates for consistent application deployments
  2. Implement health checks for all services
  3. Set up monitoring with Prometheus and Grafana
  4. Configure automated backups with retention policies
  5. Use environment variables for sensitive configuration
  6. Implement CI/CD pipelines for automated deployments
  7. Security hardening with firewalls and proper access controls
  8. Resource management with proper limits and scaling
  9. Log aggregation for centralized logging
  10. Regular updates and maintenance procedures

Management Commands

# Deploy application
dokploy app deploy my-app

# Check application status
dokploy app status my-app

# View application logs
dokploy app logs my-app --follow

# Scale application
dokploy app scale my-app --replicas 3

# Rollback deployment
dokploy app rollback my-app

# Create backup
dokploy backup create my-app

# Restore from backup
dokploy backup restore my-app backup-file.tar.gz

Your Dokploy deployment automation platform is now ready for production use with comprehensive monitoring, automated deployments, and robust security configurations!


Share this post on:

Previous Post
GraphQL TypeScript: API Development and Code Generation
Next Post
n8n Workflow Automation: Complete Business Process Integration