Back to Coolify Series
    Part 6 of 6 — Final

    Advanced Topics & Production Hardening

    Harden your infrastructure for production—lock down security, automate deployments, and build operational knowledge to troubleshoot issues fast.

    Security
    CI/CD
    ⏱️ 30 min
    1

    Wildcard SSL Certificates

    Individual certificates work fine for a few domains, but managing dozens of subdomains gets tedious. Wildcard certificates cover all subdomains with a single cert.

    Simplicity

    One cert for *.apps.domain.com

    Instant Subdomains

    No DNS propagation wait

    Cleaner Management

    Fewer certs to track

    Configure Wildcard DNS

    DNS Records
    Type: A
    Name: apps
    Value: your-coolify-server-ip
    
    Type: A  
    Name: *.apps
    Value: your-coolify-server-ip

    Enable Wildcard SSL in Coolify

    1. Go to SettingsConfiguration
    2. Set Wildcard Domain to apps.yourdomain.com
    3. Configure DNS provider for validation

    Cloudflare Configuration

    Create an API token with Zone:DNS:Edit permissions:

    SettingValue
    DNS ProviderCloudflare
    API TokenYour Cloudflare API token
    2

    Reverse Proxy Configuration

    Coolify uses Traefik as its reverse proxy. The coolify-proxy container terminates SSL, routes traffic, and handles Let's Encrypt certificates.

    Custom Traefik Labels

    docker-compose.yml labels
    services:
      myapp:
        image: myapp:latest
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.myapp.rule=Host(`myapp.yourdomain.com`)"
          - "traefik.http.routers.myapp.tls=true"
          - "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
          - "traefik.http.services.myapp.loadbalancer.server.port=3000"

    Rate Limiting

    Rate limit middleware
    labels:
      - "traefik.http.middlewares.ratelimit.ratelimit.average=100"
      - "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
      - "traefik.http.routers.myapp.middlewares=ratelimit"

    Security Headers

    Security headers middleware
    labels:
      - "traefik.http.middlewares.secheaders.headers.stsSeconds=31536000"
      - "traefik.http.middlewares.secheaders.headers.stsIncludeSubdomains=true"
      - "traefik.http.middlewares.secheaders.headers.contentTypeNosniff=true"
      - "traefik.http.middlewares.secheaders.headers.frameDeny=true"
      - "traefik.http.routers.myapp.middlewares=secheaders"

    IP Allowlisting & Basic Auth

    IP allowlist for admin panels
    labels:
      - "traefik.http.middlewares.adminonly.ipallowlist.sourcerange=10.0.0.1/32,192.168.1.0/24"
      - "traefik.http.routers.admin.middlewares=adminonly"
    Generate password hash
    htpasswd -nb admin yourpassword
    # Output: admin:$apr1$xyz...
    Basic auth middleware
    labels:
      - "traefik.http.middlewares.auth.basicauth.users=admin:$apr1$xyz..."
      - "traefik.http.routers.myapp.middlewares=auth"

    Note: Double the $ signs in compose files.

    3

    CI/CD Integration

    Automate deployments beyond Coolify's built-in Git webhooks.

    GitHub Actions Integration

    .github/workflows/deploy.yml
    name: Deploy to Coolify
    
    on:
      push:
        branches: [main]
    
    jobs:
      deploy:
        runs-on: ubuntu-latest
        steps:
          - name: Trigger Coolify Deployment
            run: |
              curl -X POST \
                -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" \
                -H "Content-Type: application/json" \
                "https://coolify.yourdomain.com/api/v1/deploy?uuid=${{ secrets.COOLIFY_APP_UUID }}"

    Setup: Generate API token in Coolify (SettingsAPI Tokens), find app UUID in settings, add both as GitHub secrets.

    GitLab CI Integration

    .gitlab-ci.yml
    deploy:
      stage: deploy
      only:
        - main
      script:
        - |
          curl -X POST \
            -H "Authorization: Bearer ${COOLIFY_TOKEN}" \
            "https://coolify.yourdomain.com/api/v1/deploy?uuid=${COOLIFY_APP_UUID}"

    Conditional Deployments

    Deploy only when tests pass
    name: Test and Deploy
    
    on:
      push:
        branches: [main]
    
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - name: Run tests
            run: npm test
    
      deploy:
        needs: test
        runs-on: ubuntu-latest
        steps:
          - name: Deploy to Coolify
            run: |
              curl -X POST \
                -H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}" \
                "https://coolify.yourdomain.com/api/v1/deploy?uuid=${{ secrets.COOLIFY_APP_UUID }}"

    Deployment Notifications

    Slack notifications
    - name: Notify Slack
      if: success()
      run: |
        curl -X POST -H 'Content-type: application/json' \
          --data '{"text":"✅ Deployed ${{ github.repository }} to production"}' \
          ${{ secrets.SLACK_WEBHOOK }}
    
    - name: Notify on Failure
      if: failure()
      run: |
        curl -X POST -H 'Content-type: application/json' \
          --data '{"text":"❌ Deployment failed for ${{ github.repository }}"}' \
          ${{ secrets.SLACK_WEBHOOK }}
    4

    Coolify API

    Coolify's API enables programmatic control over your entire infrastructure.

    Authentication

    Generate a token: SettingsAPI TokensCreate Token

    Include in requests
    curl -H "Authorization: Bearer your-api-token" \
      https://coolify.yourdomain.com/api/v1/...

    Common Operations

    List all applications
    curl -H "Authorization: Bearer $TOKEN" \
      https://coolify.yourdomain.com/api/v1/applications
    Trigger deployment
    curl -X POST -H "Authorization: Bearer $TOKEN" \
      https://coolify.yourdomain.com/api/v1/deploy?uuid={uuid}
    Start/Stop/Restart
    # Stop
    curl -X POST -H "Authorization: Bearer $TOKEN" \
      https://coolify.yourdomain.com/api/v1/applications/{uuid}/stop
    
    # Start  
    curl -X POST -H "Authorization: Bearer $TOKEN" \
      https://coolify.yourdomain.com/api/v1/applications/{uuid}/start
    
    # Restart
    curl -X POST -H "Authorization: Bearer $TOKEN" \
      https://coolify.yourdomain.com/api/v1/applications/{uuid}/restart

    Automation Scripts

    Health check and auto-restart
    #!/bin/bash
    TOKEN="your-token"
    APP_UUID="your-app-uuid"
    HEALTH_URL="https://myapp.yourdomain.com/health"
    
    if ! curl -sf "$HEALTH_URL" > /dev/null; then
      echo "Health check failed, restarting..."
      curl -X POST -H "Authorization: Bearer $TOKEN" \
        "https://coolify.yourdomain.com/api/v1/applications/$APP_UUID/restart"
    fi
    Add to cron
    */5 * * * * /path/to/health-check.sh >> /var/log/health-check.log 2>&1
    5

    Security Hardening

    SSH Hardening

    /etc/ssh/sshd_config
    PermitRootLogin prohibit-password  # Key-only root login
    PasswordAuthentication no          # Disable password auth
    PubkeyAuthentication yes
    MaxAuthTries 3
    AllowUsers root deploy             # Restrict to specific users

    Fail2ban

    Install and configure
    apt install fail2ban
    
    # /etc/fail2ban/jail.local
    [sshd]
    enabled = true
    port = 22
    maxretry = 3
    bantime = 3600
    
    systemctl enable fail2ban
    systemctl start fail2ban

    Firewall Configuration

    UFW rules for production
    # Reset to defaults
    ufw default deny incoming
    ufw default allow outgoing
    
    # Essential ports
    ufw allow 22/tcp comment 'SSH'
    ufw allow 80/tcp comment 'HTTP'
    ufw allow 443/tcp comment 'HTTPS'
    
    # Enable
    ufw enable

    Docker Security

    Run containers as non-root
    FROM node:20-alpine
    RUN addgroup -S appgroup && adduser -S appuser -G appgroup
    USER appuser
    WORKDIR /app
    COPY --chown=appuser:appgroup . .
    CMD ["node", "index.js"]
    Resource limits (always set these)
    services:
      myapp:
        image: myapp:latest
        deploy:
          resources:
            limits:
              cpus: '1'
              memory: 512M
            reservations:
              cpus: '0.25'
              memory: 128M

    Secrets Management

    • Never commit secrets to Git
    • Use Coolify's environment variables (encrypted at rest)
    • Rotate secrets regularly: Database passwords quarterly, API keys after team departures
    6

    Backup Strategy

    The 3-2-1 Rule

    • 3 copies of your data
    • 2 different storage media
    • 1 offsite location
    CopyLocationPurpose
    1Live databaseProduction
    2Local backup on serverQuick recovery
    3S3-compatible storageDisaster recovery

    Coolify Configuration Backup

    Backup script
    #!/bin/bash
    BACKUP_DIR="/backups/coolify"
    DATE=$(date +%Y%m%d)
    
    mkdir -p $BACKUP_DIR
    
    # Database
    docker exec coolify-db pg_dump -U coolify coolify > $BACKUP_DIR/coolify-db-$DATE.sql
    
    # Configuration and keys
    tar -czvf $BACKUP_DIR/coolify-config-$DATE.tar.gz /data/coolify/
    
    # Upload to S3
    aws s3 cp $BACKUP_DIR/coolify-db-$DATE.sql s3://your-bucket/coolify/
    aws s3 cp $BACKUP_DIR/coolify-config-$DATE.tar.gz s3://your-bucket/coolify/
    
    # Cleanup old local backups (keep 7 days)
    find $BACKUP_DIR -mtime +7 -delete

    Disaster Recovery Testing

    Quarterly, test your recovery: spin up fresh VPS, restore from backup, verify apps deploy, confirm data integrity. Recovery that hasn't been tested isn't recovery.

    7

    Monitoring and Alerting

    Essential Metrics

    MetricWarningCritical
    CPU usage70% sustained90% sustained
    Memory usage80%95%
    Disk usage75%90%
    Load averagecores × 1.5cores × 2

    Prometheus + Grafana Stack

    docker-compose.yml
    version: "3.8"
    
    services:
      prometheus:
        image: prom/prometheus:latest
        restart: always
        ports:
          - "9090:9090"
        volumes:
          - ./prometheus.yml:/etc/prometheus/prometheus.yml
          - prometheus-data:/prometheus
        command:
          - '--config.file=/etc/prometheus/prometheus.yml'
          - '--storage.tsdb.retention.time=30d'
    
      grafana:
        image: grafana/grafana:latest
        restart: always
        ports:
          - "3000:3000"
        environment:
          - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
        volumes:
          - grafana-data:/var/lib/grafana
    
      node-exporter:
        image: prom/node-exporter:latest
        restart: always
        ports:
          - "9100:9100"
    
      cadvisor:
        image: gcr.io/cadvisor/cadvisor:latest
        restart: always
        ports:
          - "8080:8080"
    
    volumes:
      prometheus-data:
      grafana-data:

    Alert Rules

    • Disk space < 10% remaining
    • Memory usage > 90% for 5 minutes
    • Container restart count > 3 in 10 minutes
    • HTTP 5xx errors > 10 in 1 minute
    • SSL certificate expiring in < 14 days
    8

    Troubleshooting Guide

    Container Won't Start

    Check logs
    # Via CLI
    docker logs <container-id> --tail 100
    SymptomCauseSolution
    "port already in use"Port conflictChange port or stop conflicting container
    "no space left"Disk fulldocker system prune -a
    "OOMKilled"Out of memoryIncrease memory limit
    "exec format error"Wrong architectureUse correct image (amd64 vs arm64)

    Network Issues

    Test container connectivity
    # Check containers are on same network
    docker network inspect coolify
    
    # Test connectivity from app container
    docker exec -it <app-container> ping <db-container>
    docker exec -it <app-container> nc -zv <db-container> 5432

    SSL Certificate Issues

    1. Verify DNS points to correct IP: dig yourdomain.com
    2. Check ports 80/443 are open: ufw status
    3. Review Traefik logs: docker logs coolify-proxy
    4. Verify domain is correctly set in Coolify

    Emergency: Rollback

    1. Coolify UI → Resource → Deployments
    2. Find last working deployment
    3. Click Rollback

    Complete Coolify Recovery

    If Coolify itself is broken
    cd /data/coolify/source
    docker compose down
    docker compose pull
    docker compose up -d
    9

    Maintenance Checklist

    📅 Daily

    • ☐ Check Uptime Kuma for alerts
    • ☐ Review error logs for anomalies
    • ☐ Verify backup jobs completed

    📅 Weekly

    • ☐ Review resource usage trends
    • ☐ Check disk space across servers
    • ☐ Update containers with security patches

    📅 Monthly

    • ☐ Review and rotate API tokens
    • ☐ Audit user access
    • ☐ Test one backup restoration
    • ☐ Clean up unused resources

    📅 Quarterly

    • ☐ Full disaster recovery test
    • ☐ Security audit (ports, permissions)
    • ☐ Performance review & optimization
    • ☐ Update documentation

    🎉 Series Complete!

    Over these six parts, you've built a complete self-hosted platform:

    1. Part 1: Installed Coolify on RamNode with proper DNS and SSL
    2. Part 2: Deployed applications with Git integration and custom domains
    3. Part 3: Set up databases with backups and secure networking
    4. Part 4: Deployed stacks with Docker Compose—analytics, monitoring, AI tools
    5. Part 5: Scaled across multiple servers with load balancing
    6. Part 6: Hardened for production with security, automation, and monitoring

    You now have infrastructure that rivals expensive managed platforms—but you own it completely. Your data stays on your servers, your costs are predictable, and you have full control.

    Next Steps

    • Join the Coolify community: Discord and GitHub for support
    • Contribute back: The project is open source
    • Keep learning: Kubernetes, Terraform, GitOps practices

    Thanks for following along. Now go build something great! 🚀