Operational teams lose hours every week to repetitive tasks: running the same database cleanup scripts, rotating logs across application servers, checking certificate expiration dates, and onboarding new engineers through manual checklists. When these procedures live in individual bash histories or undocumented runbooks, consistency disappears and outages become harder to debug. Rundeck is an open-source runbook automation platform that turns ad-hoc commands into scheduled, audited, and self-service jobs. Teams use it to automate deployments, enforce maintenance windows, coordinate incident response, and give developers safe access to operational actions without handing over raw SSH credentials.
In this guide, we will deploy Rundeck on a single Ubuntu host with Docker Compose, publish it through Caddy with automatic HTTPS, connect it to a PostgreSQL database for durability, and configure the operational controls that make it safe for production: secret management, database backups, health checks, and a clear acceptance checklist.
Architecture and flow overview
The deployment uses three containers on a dedicated bridge network: Rundeck, PostgreSQL, and Caddy. Rundeck serves the application on an internal port. PostgreSQL persists job definitions, execution history, user accounts, and project configuration. Caddy terminates TLS, handles certificate renewal, and proxies requests to Rundeck. Persistent data lives in named Docker volumes for the database and in a bind mount for Rundeck execution logs and file uploads. A nightly cron job dumps the PostgreSQL database to an encrypted archive on the host.
When an operator opens Rundeck in a browser, the request flows through Caddy on port 443, is decrypted, and forwarded to the Rundeck container on the internal Docker network. Rundeck queries PostgreSQL for the requested project or job, renders the execution page, and returns the HTML. When a job runs, Rundeck writes execution output to its local storage and records the result in the database. Caddy manages ACME challenges and automatically renews the TLS certificate before expiry.
- Caddy handles public HTTPS on port 443 and auto-redirects HTTP to HTTPS.
- Rundeck runs the Java application server with the built-in web UI and job execution engine.
- PostgreSQL stores projects, jobs, executions, users, groups, and ACL policies.
- Docker volumes persist the database files and Rundeck server data across restarts.
- Host cron runs
pg_dumpnightly and rotates encrypted backups locally or to S3.
Prerequisites
- Ubuntu 22.04 or 24.04 LTS with SSH access and sudo privileges.
- A DNS A record pointing
rundeck.yourdomain.comto the server public IP. - Docker Engine 24.x+ and Docker Compose plugin installed.
- Ports 80 and 443 open to Caddy for ACME challenges and HTTPS traffic.
- At least 2 GB RAM and 20 GB disk for execution logs, job artifacts, and backups.
- An SMTP provider account or local relay for password-reset and notification emails.
Step-by-step deployment
1) Install Docker, Compose, Caddy, and firewall basics
Update the package index, install Docker and the Compose plugin, and enable the firewall with only the ports Caddy needs:
sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
sudo apt install -y caddy
sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable
2) Create directories and environment file
Create a dedicated directory for Rundeck, generate strong passwords, and prepare the environment file:
sudo mkdir -p /opt/rundeck/{data,backups}
sudo chown -R $USER:$USER /opt/rundeck
cd /opt/rundeck
DB_PASS=$(openssl rand -base64 32 | tr -dc 'a-zA-Z0-9' | head -c 32)
ADMIN_PASS=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24)
cat > .env <
3) Define Compose services
The Compose file defines three services on a shared bridge network. Rundeck loads environment variables from the external .env file and mounts the local data directory for server logs and file uploads. PostgreSQL uses a named volume for durability:
cat > compose.yml <<'COMPOSE'
services:
db:
image: postgres:15-alpine
restart: unless-stopped
environment:
POSTGRES_DB: rundeck
POSTGRES_USER: rundeck
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U rundeck -d rundeck"]
interval: 10s
timeout: 5s
retries: 5
networks:
- rdecknet
rundeck:
image: rundeck/rundeck:5.3.0
restart: unless-stopped
depends_on:
db:
condition: service_healthy
env_file:
- .env
environment:
RUNDECK_DATABASE_DRIVER: org.postgresql.Driver
RUNDECK_DATABASE_USERNAME: ${DB_USER}
RUNDECK_DATABASE_PASSWORD: ${DB_PASSWORD}
RUNDECK_DATABASE_URL: jdbc:postgresql://db:5432/rundeck
RUNDECK_SERVER_ADDRESS: 0.0.0.0
volumes:
- ./data:/home/rundeck/server/data
networks:
- rdecknet
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- rdecknet
volumes:
db_data:
caddy_data:
caddy_config:
networks:
rdecknet:
driver: bridge
COMPOSE
4) Configure Caddy reverse proxy
Caddy handles TLS termination and proxies requests to the Rundeck container. Create the Caddyfile with the reverse proxy block and security headers:
cat > Caddyfile <<'CADDY'
rundeck.yourdomain.com {
reverse_proxy rundeck:4440 {
header_up Host {host}
header_up X-Real-IP {remote}
header_up X-Forwarded-For {remote}
header_up X-Forwarded-Proto {scheme}
}
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
CADDY
5) Start services and verify health
Pull the images, start the stack, and confirm that PostgreSQL and Rundeck reach a healthy state:
docker compose pull
docker compose up -d
sleep 20
docker compose ps
docker compose logs --tail 50 rundeck
6) First-time admin setup
Open https://rundeck.yourdomain.com in a browser. Log in with the credentials from your .env file. After login, create your first project, define a node source, and test a simple command job such as uptime on the local node. Verify that the execution completes and output appears in the activity stream.
7) Backup script
Create a nightly backup script that dumps the PostgreSQL database and archives the Rundeck data directory. Store the script in /opt/rundeck/backups/backup.sh:
cat > /opt/rundeck/backups/backup.sh <<'BKP'
#!/bin/bash
set -euo pipefail
BACKUP_DIR="/opt/rundeck/backups"
DATE=$(date +%Y%m%d_%H%M%S)
source /opt/rundeck/.env
# Database dump
docker compose -f /opt/rundeck/compose.yml exec -T db pg_dump -U "$DB_USER" -d "$DB_NAME" | gzip > "$BACKUP_DIR/rundeck_db_$DATE.sql.gz"
# Data directory archive
tar czf "$BACKUP_DIR/rundeck_data_$DATE.tar.gz" -C /opt/rundeck data
# Rotate to keep last 14 days
find "$BACKUP_DIR" -name 'rundeck_*' -mtime +14 -delete
BKP
chmod +x /opt/rundeck/backups/backup.sh
# Schedule nightly at 02:00
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/rundeck/backups/backup.sh >> /opt/rundeck/backups/backup.log 2>&1") | crontab -
8) Acceptance checklist execution
Run through the checklist to confirm the deployment is production-ready:
- DNS resolves
rundeck.yourdomain.comto the server public IP. curl -I https://rundeck.yourdomain.comreturns HTTP 200 with HSTS headers.- Login succeeds with the admin credentials defined in
.env. - Create a test project and run a local command job successfully.
- Execution logs appear in the job output page without errors.
- A backup archive exists in
/opt/rundeck/backups/after the first scheduled run.
Configuration and secrets handling
Never commit the .env file to version control. Store it in a secrets manager or encrypted backup. The admin password grants full access to all projects and executions; rotate it quarterly and require MFA if you upgrade to Rundeck Enterprise. For database credentials, create a dedicated PostgreSQL user with limited privileges rather than using the superuser account.
Backup archives should be encrypted at rest if they are copied to object storage. Consider restricting Rundeck admin access to a VPN or IP allowlist. Execution logs may contain sensitive command output; review your log retention policy and scrub passwords from job definitions before sharing output with external teams. If you connect Rundeck to remote nodes, use SSH key authentication with dedicated service accounts rather than shared passwords.
Verification
- Run
curl -I https://rundeck.yourdomain.comand confirm HTTP 200 withstrict-transport-securityheader. - Log in with the admin credentials and verify the dashboard loads.
- Create a project and run a simple command job on the local node.
- Inspect the execution output and confirm logs are recorded.
- Check
/opt/rundeck/backups/for a recent.sql.gzarchive. - Verify that Caddy has obtained a valid certificate with
docker compose logs caddy.
Common issues and fixes
Rundeck shows a database connection error on first start: Ensure PostgreSQL has finished initializing before Rundeck starts. The depends_on condition service_healthy handles this, but if you see repeated connection failures, run docker compose restart rundeck after confirming docker compose logs db shows database system is ready.
Jobs hang with no output: The default local node executor runs commands inside the container. If you need to execute commands on the Docker host, configure the node executor to use SSH or docker-exec, or bind-mount the host Docker socket with caution.
Caddy returns 502 Bad Gateway: Verify that the Caddyfile proxy target matches the Compose service name rundeck on port 4440. Check docker compose logs rundeck to confirm the application is listening.
Out-of-memory errors during large executions: Rundeck is a Java application. Increase the heap size by setting JVM_OPTIONS in the environment, for example -Xmx2g, and ensure the host has at least 4 GB RAM.
Certificate renewal fails behind a restrictive firewall: Caddy needs outbound HTTPS to the Let's Encrypt or ZeroSSL ACME servers. Whitelist acme-v02.api.letsencrypt.org and the relevant OCSP responders.
File uploads fail with permission denied: The Rundeck container runs as a non-root user. Ensure /opt/rundeck/data is writable by UID 1000 or the user that the container runs as.
FAQ
Can I use MySQL or MariaDB instead of PostgreSQL?
Yes. Rundeck supports MySQL, MariaDB, PostgreSQL, and H2. To use MariaDB, change the RUNDECK_DATABASE_DRIVER to org.mariadb.jdbc.Driver, update the JDBC URL, and replace the Compose database service with the MariaDB image. PostgreSQL is recommended for large execution histories and long-term stability.
How do I add remote nodes to Rundeck?
Define node sources in your project settings. Rundeck supports static YAML or XML resources, Ansible inventory, AWS EC2 tags, Kubernetes pods, and more. For each node, specify the hostname, username, and SSH key path. Test connectivity with the built-in node executor before scheduling jobs.
Does Rundeck support LDAP and SSO authentication?
Yes. Rundeck Community supports LDAP, Active Directory, PAM, and OIDC through Java container login modules. Rundeck Enterprise adds SAML, Okta, and Azure AD integration with group synchronization and fine-grained ACL policies.
How do I schedule a job to run on a cron expression?
In the job editor, enable scheduling and enter a standard cron expression such as 0 2 * * * for nightly execution. Rundeck stores the schedule in the database and triggers jobs from the internal Quartz scheduler. You can also enable calendar-based exclusions for maintenance windows.
What is the difference between Rundeck Community and Enterprise?
Rundeck Community is free, open-source, and includes job scheduling, node management, and basic ACLs. Rundeck Enterprise adds clustering for high availability, advanced reporting, workflow strategies, enterprise SSO, and commercial support. This guide uses the Community edition.
Can I trigger jobs via webhook or API?
Yes. Every job can be exposed as a webhook URL or triggered through the REST API using a token. This is useful for CI/CD pipelines, chatops integrations, and external monitoring systems. Generate API tokens in the user profile and set ACL policies that limit token scope.
How do I update Rundeck?
Pull the latest image, recreate the container, and verify the version on the footer of the login page. Because the data directory and database are persistent, the update is non-destructive. Always take a backup before major upgrades.
docker compose pull rundeck
docker compose up -d rundeck
Internal links
- Production Guide: Deploy n8n with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
- Production Guide: Deploy Semaphore UI with Docker Compose + Caddy + PostgreSQL on Ubuntu
- Production Guide: Deploy Vaultwarden with Docker Compose + Caddy on Ubuntu
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.
Header image: Original SysBrix generated header, no watermark.