Deployment
This guide covers deploying Dagu in production environments.
Deployment Methods
Systemd Service
Create a systemd service for automatic startup:
ini
# /etc/systemd/system/dagu.service
[Unit]
Description=Dagu Workflow Engine
After=network.target
Documentation=https://docs.dagu.cloud
[Service]
Type=simple
User=dagu
Group=dagu
WorkingDirectory=/opt/dagu
ExecStart=/usr/local/bin/dagu start-all --config /etc/dagu/config.yaml
Restart=always
RestartSec=10
# Security
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/dagu /var/log/dagu
# Environment
Environment="DAGU_HOST=0.0.0.0"
Environment="DAGU_PORT=8080"
EnvironmentFile=-/etc/dagu/env
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=dagu
[Install]
WantedBy=multi-user.target
Enable and start:
bash
sudo systemctl daemon-reload
sudo systemctl enable dagu
sudo systemctl start dagu
sudo systemctl status dagu
Docker
Run with Docker:
bash
docker run -d \
--name dagu \
-p 8080:8080 \
-v $HOME/.config/dagu:/config \
-e DAGU_HOST=0.0.0.0 \
-e DAGU_PORT=8080 \
--restart always \
ghcr.io/dagu-org/dagu:latest
Docker Compose
Basic deployment using the official Docker image:
yaml
# docker-compose.yml
services:
dagu:
image: "ghcr.io/dagu-org/dagu:latest"
container_name: dagu
hostname: dagu
restart: always
ports:
- "8080:8080"
environment:
- DAGU_PORT=8080
- DAGU_TZ=Asia/Tokyo # Set your timezone
- DAGU_BASE_PATH=/ # Change if using reverse proxy
- PUID=1000 # User ID for file permissions
- PGID=1000 # Group ID for file permissions
volumes:
- dagu_config:/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/status"]
interval: 30s
timeout: 10s
retries: 3
volumes:
dagu_config: {}
For custom configuration with separate volumes:
yaml
# docker-compose.yml with custom paths
services:
dagu:
image: "ghcr.io/dagu-org/dagu:latest"
container_name: dagu
restart: always
ports:
- "8080:8080"
volumes:
- ./dags:/config/dags
- ./logs:/config/logs
- ./data:/config/data
- ./config.yaml:/config/config.yaml:ro
environment:
- DAGU_HOST=0.0.0.0
- DAGU_PORT=8080
- DAGU_CONFIG=/config/config.yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/status"]
interval: 30s
timeout: 10s
retries: 3
For Docker-in-Docker support (to run Docker executors):
yaml
# docker-compose.yml with Docker-in-Docker
services:
dagu:
image: "ghcr.io/dagu-org/dagu:latest"
container_name: dagu
hostname: dagu
restart: always
ports:
- "8080:8080"
environment:
- DAGU_PORT=8080
- DAGU_TZ=Asia/Tokyo
- DAGU_BASE_PATH=/
volumes:
- dagu_config:/config
- /var/run/docker.sock:/var/run/docker.sock
user: "0:0"
entrypoint: []
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/api/v1/status"]
interval: 30s
timeout: 10s
retries: 3
volumes:
dagu_config: {}
Kubernetes
Deploy on Kubernetes:
yaml
# dagu-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: dagu
labels:
app: dagu
spec:
replicas: 1
selector:
matchLabels:
app: dagu
template:
metadata:
labels:
app: dagu
spec:
containers:
- name: dagu
image: ghcr.io/dagu-org/dagu:latest
ports:
- containerPort: 8080
env:
- name: DAGU_HOST
value: "0.0.0.0"
- name: DAGU_PORT
value: "8080"
volumeMounts:
- name: dags
mountPath: /app/dags
- name: logs
mountPath: /app/logs
- name: config
mountPath: /app/config.yaml
subPath: config.yaml
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /api/v1/status
port: 8080
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: dags
persistentVolumeClaim:
claimName: dagu-dags-pvc
- name: logs
persistentVolumeClaim:
claimName: dagu-logs-pvc
- name: config
configMap:
name: dagu-config
---
apiVersion: v1
kind: Service
metadata:
name: dagu
spec:
selector:
app: dagu
ports:
- port: 8080
targetPort: 8080
type: LoadBalancer
Reverse Proxy
Nginx
nginx
# /etc/nginx/sites-available/dagu
server {
listen 80;
server_name dagu.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name dagu.example.com;
ssl_certificate /etc/letsencrypt/live/dagu.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dagu.example.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_read_timeout 86400;
}
}
Apache
apache
# /etc/apache2/sites-available/dagu.conf
<VirtualHost *:80>
ServerName dagu.example.com
Redirect permanent / https://dagu.example.com/
</VirtualHost>
<VirtualHost *:443>
ServerName dagu.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/dagu.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/dagu.example.com/privkey.pem
ProxyPreserveHost On
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
# WebSocket
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:8080/$1" [P,L]
</VirtualHost>
Traefik
yaml
# docker-compose.yml with Traefik
version: '3.8'
services:
traefik:
image: traefik:v2.10
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik.yml:/traefik.yml:ro
- ./acme.json:/acme.json
labels:
- "traefik.enable=true"
dagu:
image: ghcr.io/dagu-org/dagu:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.dagu.rule=Host(`dagu.example.com`)"
- "traefik.http.routers.dagu.entrypoints=websecure"
- "traefik.http.routers.dagu.tls.certresolver=letsencrypt"
- "traefik.http.services.dagu.loadbalancer.server.port=8080"
High Availability
Multi-Node Setup
Configure multiple Dagu instances:
yaml
# Primary node
host: 0.0.0.0
port: 8080
paths:
dagsDir: /shared/dags # Shared storage
dataDir: /shared/data
logDir: /shared/logs
# Secondary node (read-only)
host: 0.0.0.0
port: 8081
permissions:
writeDAGs: false
runDAGs: false
paths:
dagsDir: /shared/dags
dataDir: /shared/data
logDir: /shared/logs
Load Balancer
HAProxy configuration:
# /etc/haproxy/haproxy.cfg
global
daemon
defaults
mode http
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend dagu_frontend
bind *:80
default_backend dagu_backend
backend dagu_backend
balance roundrobin
option httpchk GET /api/v1/status
server dagu1 192.168.1.10:8080 check
server dagu2 192.168.1.11:8080 check backup
Monitoring
Health Checks
yaml
# Kubernetes readiness probe
readinessProbe:
httpGet:
path: /api/v1/status
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
# Docker healthcheck
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8080/api/v1/status"]
interval: 30s
timeout: 10s
retries: 3
Prometheus Metrics
Export metrics via API:
yaml
steps:
- name: export metrics
command: |
curl -s http://localhost:8080/api/v1/dags | \
jq -r '.[] | "\(.name) \(.status)"' | \
awk '{print "dagu_dag_status{name=\""$1"\",status=\""$2"\"} 1"}' > \
/var/lib/prometheus/node_exporter/dagu.prom
schedule: "* * * * *"
Logging
Configure centralized logging:
yaml
# Fluentd configuration
<source>
@type tail
path /var/log/dagu/*.log
pos_file /var/log/fluentd/dagu.pos
tag dagu.*
<parse>
@type json
</parse>
</source>
<match dagu.**>
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix dagu
</match>
Security Hardening
Network Security
yaml
# Bind to localhost only
host: 127.0.0.1
port: 8080
# Use reverse proxy for external access
File Permissions
bash
# Create dedicated user
sudo useradd -r -s /bin/false dagu
# Set ownership
sudo chown -R dagu:dagu /opt/dagu
sudo chmod 750 /opt/dagu
# Secure configuration
sudo chmod 600 /etc/dagu/config.yaml
sudo chown dagu:dagu /etc/dagu/config.yaml
Resource Limits
yaml
# systemd resource limits
[Service]
# Memory
MemoryMax=2G
MemoryHigh=1G
# CPU
CPUQuota=100%
# File descriptors
LimitNOFILE=65536
# Process limits
LimitNPROC=4096
Backup & Recovery
Backup Script
bash
#!/bin/bash
# /opt/dagu/backup.sh
BACKUP_DIR="/backup/dagu"
DATE=$(date +%Y%m%d_%H%M%S)
# Create backup
tar -czf "$BACKUP_DIR/dagu_backup_$DATE.tar.gz" \
/opt/dagu/dags \
/var/lib/dagu/data \
/var/log/dagu \
/etc/dagu/config.yaml
# Keep last 30 days
find "$BACKUP_DIR" -name "dagu_backup_*.tar.gz" -mtime +30 -delete
Restore Procedure
bash
# Stop Dagu
sudo systemctl stop dagu
# Restore from backup
tar -xzf /backup/dagu/dagu_backup_20240115_120000.tar.gz -C /
# Start Dagu
sudo systemctl start dagu
Performance Tuning
System Limits
bash
# /etc/security/limits.d/dagu.conf
dagu soft nofile 65536
dagu hard nofile 65536
dagu soft nproc 32768
dagu hard nproc 32768
Kernel Parameters
bash
# /etc/sysctl.d/dagu.conf
# Increase file watchers
fs.inotify.max_user_watches=524288
# Network tuning
net.core.somaxconn=65535
net.ipv4.tcp_max_syn_backlog=65535
Troubleshooting
Common Issues
Port Already in Use
bash# Find process using port sudo lsof -i :8080 # Kill process sudo kill -9 <PID>
Permission Denied
bash# Fix permissions sudo chown -R dagu:dagu /opt/dagu sudo chmod -R 755 /opt/dagu/dags
Memory Issues
bash# Check memory usage ps aux | grep dagu # Increase limits sudo systemctl edit dagu # Add: MemoryMax=4G
Debug Mode
Enable debug logging:
yaml
# config.yaml
debug: true
logFormat: json
Or via command line:
bash
dagu start-all --debug --log-format=json