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
Type: A
Name: apps
Value: your-coolify-server-ip
Type: A
Name: *.apps
Value: your-coolify-server-ipEnable Wildcard SSL in Coolify
- Go to Settings → Configuration
- Set Wildcard Domain to
apps.yourdomain.com - Configure DNS provider for validation
Cloudflare Configuration
Create an API token with Zone:DNS:Edit permissions:
| Setting | Value |
|---|---|
| DNS Provider | Cloudflare |
| API Token | Your Cloudflare API token |
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
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
labels:
- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"
- "traefik.http.routers.myapp.middlewares=ratelimit"Security Headers
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
labels:
- "traefik.http.middlewares.adminonly.ipallowlist.sourcerange=10.0.0.1/32,192.168.1.0/24"
- "traefik.http.routers.admin.middlewares=adminonly"htpasswd -nb admin yourpassword
# Output: admin:$apr1$xyz...labels:
- "traefik.http.middlewares.auth.basicauth.users=admin:$apr1$xyz..."
- "traefik.http.routers.myapp.middlewares=auth"Note: Double the $ signs in compose files.
CI/CD Integration
Automate deployments beyond Coolify's built-in Git webhooks.
GitHub Actions Integration
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 (Settings → API Tokens), find app UUID in settings, add both as GitHub secrets.
GitLab CI Integration
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
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
- 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 }}Coolify API
Coolify's API enables programmatic control over your entire infrastructure.
Authentication
Generate a token: Settings → API Tokens → Create Token
curl -H "Authorization: Bearer your-api-token" \
https://coolify.yourdomain.com/api/v1/...Common Operations
curl -H "Authorization: Bearer $TOKEN" \
https://coolify.yourdomain.com/api/v1/applicationscurl -X POST -H "Authorization: Bearer $TOKEN" \
https://coolify.yourdomain.com/api/v1/deploy?uuid={uuid}# 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}/restartAutomation Scripts
#!/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*/5 * * * * /path/to/health-check.sh >> /var/log/health-check.log 2>&1Security Hardening
SSH Hardening
PermitRootLogin prohibit-password # Key-only root login
PasswordAuthentication no # Disable password auth
PubkeyAuthentication yes
MaxAuthTries 3
AllowUsers root deploy # Restrict to specific usersFail2ban
apt install fail2ban
# /etc/fail2ban/jail.local
[sshd]
enabled = true
port = 22
maxretry = 3
bantime = 3600
systemctl enable fail2ban
systemctl start fail2banFirewall Configuration
# 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 enableDocker Security
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"]services:
myapp:
image: myapp:latest
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.25'
memory: 128MSecrets 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
Backup Strategy
The 3-2-1 Rule
- 3 copies of your data
- 2 different storage media
- 1 offsite location
| Copy | Location | Purpose |
|---|---|---|
| 1 | Live database | Production |
| 2 | Local backup on server | Quick recovery |
| 3 | S3-compatible storage | Disaster recovery |
Coolify Configuration Backup
#!/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 -deleteDisaster 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.
Monitoring and Alerting
Essential Metrics
| Metric | Warning | Critical |
|---|---|---|
| CPU usage | 70% sustained | 90% sustained |
| Memory usage | 80% | 95% |
| Disk usage | 75% | 90% |
| Load average | cores × 1.5 | cores × 2 |
Prometheus + Grafana Stack
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
Troubleshooting Guide
Container Won't Start
# Via CLI
docker logs <container-id> --tail 100| Symptom | Cause | Solution |
|---|---|---|
| "port already in use" | Port conflict | Change port or stop conflicting container |
| "no space left" | Disk full | docker system prune -a |
| "OOMKilled" | Out of memory | Increase memory limit |
| "exec format error" | Wrong architecture | Use correct image (amd64 vs arm64) |
Network Issues
# 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> 5432SSL Certificate Issues
- Verify DNS points to correct IP:
dig yourdomain.com - Check ports 80/443 are open:
ufw status - Review Traefik logs:
docker logs coolify-proxy - Verify domain is correctly set in Coolify
Emergency: Rollback
- Coolify UI → Resource → Deployments
- Find last working deployment
- Click Rollback
Complete Coolify Recovery
cd /data/coolify/source
docker compose down
docker compose pull
docker compose up -dMaintenance 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:
- Part 1: Installed Coolify on RamNode with proper DNS and SSL
- Part 2: Deployed applications with Git integration and custom domains
- Part 3: Set up databases with backups and secure networking
- Part 4: Deployed stacks with Docker Compose—analytics, monitoring, AI tools
- Part 5: Scaled across multiple servers with load balancing
- 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! 🚀
