Skip to main content

Command Palette

Search for a command to run...

Migrating Rundeck from H2 to PostgreSQL: A Complete Guide

Step-by-step walkthrough — from zero-downtime backup to production-ready database switch

Updated
8 min read

If you've been running Rundeck with its default H2 embedded database, you've probably already hit the wall: slow queries as job history grows, no proper connection pooling, and the constant anxiety of a file-based database in production. PostgreSQL fixes all of that.

This guide walks you through two migration paths — pick the one that fits your situation.


Why Migrate Away from H2?

Rundeck ships with H2 for a reason: zero-config, works out of the box. But H2 has real limitations at scale:

  • No concurrent writes — H2 struggles under parallel job execution

  • File corruption risk — a hard shutdown can corrupt the .mv.db file

  • No external tooling — can't use pg_dump, replication, or your existing DB monitoring

  • Memory pressure — H2 loads data into the JVM heap

PostgreSQL gives you connection pooling, proper ACID guarantees, pg_dump for backups, and the ability to run Standby replicas. For any team running more than a handful of jobs, it's the right call.


Before You Start: Understand What Lives in the Database

Rundeck stores the following in its database:

Data Type Migration Notes
Project definitions Exportable via API archive
Job definitions Included in project export
Execution history & logs Log files stay on disk; metadata in DB
Users & ACL tokens Recreate manually or via config
Scheduled jobs Preserved in job definitions

Execution log content lives in /var/lib/rundeck/logs/, not the database — so you don't need to migrate that.


Two Migration Strategies

Best for: Most teams. You keep all your job definitions and project configs. You lose execution history records in the DB (but not the actual log files on disk).

Effort: Low | Risk: Low | Downtime: ~30 minutes

Strategy B: Full H2 → PostgreSQL Data Migration

Best for: Teams that need to preserve execution history records in the DB for audit/compliance.

Effort: High | Risk: Medium | Downtime: 1–3 hours


Strategy A: Clean Start + Project Import

Step 1 — Back Up Everything

# Stop Rundeck first
systemctl stop rundeckd

# Back up config files
cp /etc/rundeck/rundeck-config.properties /backup/
cp /etc/rundeck/framework.properties /backup/

# Back up the H2 database files (just in case)
cp -r /var/lib/rundeck/data /backup/rundeck-data-h2/

# Back up project files on disk
cp -r /var/lib/rundeck/projects /backup/rundeck-projects/
cp -r /var/lib/rundeck/logs /backup/rundeck-logs/

Export each project via the Rundeck API (Rundeck must be running for this):

# Start Rundeck temporarily to export
systemctl start rundeckd

# Export all projects via API
TOKEN="your-api-token"
RUNDECK_URL="http://localhost:4440"

for project in \((curl -s -H "X-Rundeck-Auth-Token: \)TOKEN" \
  "$RUNDECK_URL/api/41/projects" | jq -r '.[].name'); do
  echo "Exporting: $project"
  curl -s -H "X-Rundeck-Auth-Token: $TOKEN" \
    "\(RUNDECK_URL/api/41/project/\)project/export" \
    -o "/backup/project-${project}.zip"
done

# Stop again before DB switch
systemctl stop rundeckd

Step 2 — Install and Configure PostgreSQL

# Ubuntu/Debian
apt-get install -y postgresql postgresql-contrib

# CentOS/RHEL 8+
dnf install -y postgresql-server postgresql-contrib
postgresql-setup --initdb
systemctl enable --now postgresql

Create the Rundeck database and user:

su - postgres
psql << 'EOF'
CREATE DATABASE rundeck ENCODING 'UTF8';
CREATE USER rundeckuser WITH PASSWORD 'your_strong_password_here';
GRANT ALL PRIVILEGES ON DATABASE rundeck TO rundeckuser;
\c rundeck
GRANT ALL PRIVILEGES ON SCHEMA public TO rundeckuser;
EOF

Step 3 — Configure pg_hba.conf

Edit /etc/postgresql/<version>/main/pg_hba.conf (Debian/Ubuntu) or /var/lib/pgsql/data/pg_hba.conf (RHEL):

# Allow rundeckuser from localhost
host    rundeck         rundeckuser     127.0.0.1/32    scram-sha-256
host    rundeck         rundeckuser     ::1/128         scram-sha-256

Reload PostgreSQL:

systemctl reload postgresql

Verify connectivity:

psql -U rundeckuser -h 127.0.0.1 -d rundeck -c "SELECT version();"

Step 4 — Update rundeck-config.properties

Open /etc/rundeck/rundeck-config.properties and replace the H2 datasource block:

# === REMOVE or comment out the H2 config ===
# dataSource.url = jdbc:h2:file:/var/lib/rundeck/data/rundeckdb;...

# === ADD PostgreSQL config ===
dataSource.driverClassName = org.postgresql.Driver
dataSource.url = jdbc:postgresql://127.0.0.1:5432/rundeck
dataSource.username = rundeckuser
dataSource.password = your_strong_password_here
dataSource.dialect = org.hibernate.dialect.PostgreSQLDialect

# Connection pool tuning (recommended)
dataSource.properties.maxActive = 50
dataSource.properties.maxIdle = 25
dataSource.properties.minIdle = 5
dataSource.properties.initialSize = 5
dataSource.properties.validationQuery = SELECT 1
dataSource.properties.testOnBorrow = true

Note: Rundeck 4.x+ bundles the PostgreSQL JDBC driver — no manual download needed. For older versions, download postgresql-<version>.jar and place it in /var/lib/rundeck/lib/.


Step 5 — Start Rundeck and Let It Initialize

systemctl start rundeckd

# Watch the logs for successful schema creation
tail -f /var/log/rundeck/service.log

Look for:

Hibernate: create table rduser ...
Grails application running at http://localhost:4440/

If you see Connection refused or FATAL: password authentication failed, double-check pg_hba.conf and your credentials.


Step 6 — Import Projects Back

TOKEN="your-new-api-token"
RUNDECK_URL="http://localhost:4440"

for zipfile in /backup/project-*.zip; do
  project=\((basename "\)zipfile" .zip | sed 's/project-//')
  echo "Importing: $project"

  # Create project first if it doesn't exist
  curl -s -X POST \
    -H "X-Rundeck-Auth-Token: $TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"name\":\"$project\"}" \
    "$RUNDECK_URL/api/41/projects"

  # Import archive
  curl -s -X POST \
    -H "X-Rundeck-Auth-Token: $TOKEN" \
    -H "Content-Type: application/zip" \
    --data-binary "@$zipfile" \
    "\(RUNDECK_URL/api/41/project/\)project/import?importExecutions=true"
done

Strategy B: Full Data Migration from H2

Use this only if you need historical execution records preserved in the database.

The Core Challenge

H2 and PostgreSQL have different SQL dialects. H2's SCRIPT TO command exports valid H2 SQL, but that SQL won't run cleanly in PostgreSQL without transformation.

Step 1 — Export H2 with the H2 Console

Identify the H2 version Rundeck is using:

find /var/lib/rundeck -name "h2-*.jar" 2>/dev/null
# or
ls /var/lib/rundeck/bootstrap/

Run the H2 Script tool with the matching version:

systemctl stop rundeckd

java -cp /path/to/h2-<version>.jar org.h2.tools.Script \
  -url "jdbc:h2:/var/lib/rundeck/data/rundeckdb" \
  -user sa \
  -password "" \
  -script /tmp/rundeck_h2_export.sql

Step 2 — Transform the SQL

cp /tmp/rundeck_h2_export.sql /tmp/rundeck_pg_import.sql

# Remove H2-specific statements
sed -i '/^SET /d' /tmp/rundeck_pg_import.sql
sed -i '/^CREATE USER IF NOT EXISTS/d' /tmp/rundeck_pg_import.sql
sed -i '/^ALTER TABLE.*ADD CONSTRAINT.*CHECK/d' /tmp/rundeck_pg_import.sql

# Fix identity columns
sed -i 's/BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH [0-9]* INCREMENT BY 1)/BIGSERIAL/g' \
  /tmp/rundeck_pg_import.sql

# Fix boolean literals
sed -i "s/\bTRUE\b/true/g; s/\bFALSE\b/false/g" /tmp/rundeck_pg_import.sql

Tip: H2 exports may include vendor-specific syntax that varies by version. Always test imports in a staging environment first and fix errors iteratively.

Step 3 — Import to PostgreSQL

psql -U rundeckuser -d rundeck -f /tmp/rundeck_pg_import.sql 2>&1 | tee /tmp/import_log.txt

# Check for errors
grep -i "error\|fatal" /tmp/import_log.txt

Fix any errors, then update rundeck-config.properties (same as Strategy A, Step 4) and restart.


Post-Migration Verification

Run this checklist after switching to PostgreSQL:

# 1. Service status
systemctl status rundeckd

# 2. Database tables created
psql -U rundeckuser -d rundeck -c "\dt" | wc -l
# Should show 30+ tables

# 3. Check for connection errors in logs
grep -i "HikariPool\|Cannot acquire connection\|ORA-\|PSQLException" \
  /var/log/rundeck/service.log | tail -20

# 4. Test a job execution end-to-end
# 5. Verify ACL tokens still work
# 6. Confirm scheduled jobs are firing

Performance Tuning Tips

Once you're on PostgreSQL, a few quick wins:

Connection pooling — Rundeck uses HikariCP internally. The defaults are conservative; bump them for busy instances:

dataSource.properties.maximumPoolSize = 50
dataSource.properties.minimumIdle = 10
dataSource.properties.connectionTimeout = 30000
dataSource.properties.idleTimeout = 600000

PostgreSQL autovacuum — Execution history tables grow fast. Make sure autovacuum is running and work_mem is set appropriately in postgresql.conf:

work_mem = 64MB
maintenance_work_mem = 256MB
autovacuum = on

Indexes — If execution history queries are slow, check that the execution table indexes on project, date_started, and status exist.


Common Errors and Fixes

Error Cause Fix
FATAL: password authentication failed Wrong password or pg_hba auth method Check credentials + pg_hba.conf
Connection refused (127.0.0.1:5432) PostgreSQL not running systemctl start postgresql
Permission denied for schema public PostgreSQL 15+ changed defaults Run GRANT ALL ON SCHEMA public TO rundeckuser
No suitable driver found Missing JDBC jar (old Rundeck) Add postgresql.jar to /var/lib/rundeck/lib/
Table already exists Partial init ran before Drop and recreate the database, restart clean
UnsupportedOperationException: dialect Missing dialect config Add dataSource.dialect = org.hibernate.dialect.PostgreSQLDialect

Wrapping Up

Migrating Rundeck to PostgreSQL is a one-time investment that pays off immediately in stability, performance, and operational visibility. Strategy A (clean start + project import) is the pragmatic path for most teams and can be done in under an hour.

If you're running Rundeck on Kubernetes (KubernetesExecutor or similar), the same rundeck-config.properties approach applies — just mount the config as a Secret and point the JDBC URL at your PostgreSQL service.

More from this blog

GitHub 开源项目仓库汇总(2026-04-18 更新 | 新增 6 项)

GitHub 开源项目仓库汇总(2026-04-18 更新) 数据来源:IMA 知识库 GitHub 相关内容整理 | 每周自动更新 本期新增 6 个项目,总计收录 29 个优质开源项目 📊 本周更新亮点 本周新增 6 个项目,包括: AI Coding 多 Agent 协调平台 multica(本周 +5,362 stars) AI 持久记忆框架 MemPalace(43k+ stars) AI 编码工作流编排器 Archon(17k+ stars) 全场景具身机器人数据集 AGIBO...

Apr 18, 20262 min read1

📦 Python Tools 知识库汇总 - 从 IMA 知识库梳理的实用工具

📦 Python Tools 知识库汇总 本文是从 IMA 个人知识库中梳理出的 Python 相关工具和库,涵盖网络请求、数据处理、可视化等多个领域。 概述 定期整理知识库是保持技术敏感度的重要习惯。本文汇总了从 IMA 知识库中发现的 7 个实用 Python 工具,按功能分类整理,方便大家根据需求快速查找。 网络请求与爬虫 1. Niquests 定位: 全新的 Python HTTP 客户端项目 特点: 高性能实现 API 与 requests 高度兼容 适合从 request...

Apr 17, 20262 min read
A

Agile Robin

40 posts

living an Awesome Life