Installation
cronpad is distributed as a container image and as static binaries for Linux and macOS. Every method needs only a persistent local directory for cronpad’s files: the two databases and the rebuildable queue file. There is no other service to run. Run exactly one cronpad process per data directory.
Docker Compose (recommended)
# docker-compose.yml
services:
cronpad:
image: ghcr.io/gowthamgts/cronpad:1
ports: ["8080:8080"]
env_file: .env
environment:
CRONPAD_DATABASE__PATH: /data/cronpad.db
CRONPAD_DATABASE__HISTORY_PATH: /data/history.db
volumes: ["cronpad-data:/data"] # also holds the queue, /data/cronpad-queue.db
restart: unless-stopped
volumes:
cronpad-data:
docker compose up -d
For a local-only demo including a SeaweedFS S3 server, bucket setup and development login, use:
docker compose --env-file deploy/demo.env -f deploy/docker-compose.yml --profile demo up --build
The demo uses fixed local credentials; replace them and configure OAuth for production. Without LITESTREAM_REPLICA_URL, cronpad runs SQLite only: fine for trying it out, but nothing is replicated.
The repository’s deploy/docker-compose.yml also has optional profiles: --profile backup adds a SeaweedFS S3 server and bucket setup (set the Litestream URL and credentials, or use deploy/demo.env), and --profile observability adds an OpenTelemetry Collector, Prometheus and Grafana with a ready-made dashboard.
Docker
docker run -d --name cronpad -p 8080:8080 \
-v cronpad-data:/data \
-e CRONPAD_SERVER__PUBLIC_URL=https://cron.example.com \
-e CRONPAD_AUTH__GITHUB__CLIENT_ID=… -e CRONPAD_AUTH__GITHUB__CLIENT_SECRET=… \
-e CRONPAD_SECRETS__ENCRYPTION_KEY=… \
ghcr.io/gowthamgts/cronpad:1
The published image is Alpine-based and includes /bin/sh and curl; enable the command sink with CRONPAD_SINKS__COMMAND__ENABLED=true, and add any other tools your jobs need in your own image.
Kubernetes
The essentials (a complete example is in deploy/kubernetes/):
apiVersion: apps/v1
kind: Deployment
metadata: {name: cronpad}
spec:
replicas: 1 # exactly one: SQLite has a single writer
strategy: {type: Recreate} # never run old and new pods at the same time
selector: {matchLabels: {app: cronpad}}
template:
metadata: {labels: {app: cronpad}}
spec:
containers:
- name: cronpad
image: ghcr.io/gowthamgts/cronpad:1
ports: [{containerPort: 8080}]
envFrom: [{secretRef: {name: cronpad-env}}]
volumeMounts: [{name: data, mountPath: /data}]
livenessProbe: {httpGet: {path: /healthz, port: 8080}}
readinessProbe: {httpGet: {path: /readyz, port: 8080}}
terminationGracePeriodSeconds: 45 # > server.shutdown_grace
volumes:
- name: data
persistentVolumeClaim: {claimName: cronpad-data} # ReadWriteOnce, block storage
Binary
curl -fsSLO https://github.com/gowthamgts/cronpad/releases/latest/download/cronpad_linux_amd64.tar.gz
tar xzf cronpad_linux_amd64.tar.gz
./cronpad serve --config ./cronpad.toml
A systemd unit:
[Unit]
Description=cronpad
After=network-online.target
[Service]
ExecStart=/usr/local/bin/cronpad serve --config /etc/cronpad/cronpad.toml
EnvironmentFile=/etc/cronpad/env
User=cronpad
WorkingDirectory=/var/lib/cronpad
Restart=always
TimeoutStopSec=45
[Install]
WantedBy=multi-user.target
Upgrading from a Redis-based version
Earlier releases used Redis for the queue. Current releases ignore it: on the first start after upgrading, cronpad creates the queue file next to cronpad.db (cronpad-queue.db) and rebuilds it from your jobs, retries and pending manual runs, so nothing is lost. A leftover [redis] section or CRONPAD_REDIS__* variable only logs a warning; remove it, then stop the Redis service.
Behind a reverse proxy
Set server.public_url to the external URL and list the proxy’s addresses in server.trusted_proxies so client IPs are logged and rate-limited correctly. The proxy must pass through Authorization headers and allow request bodies up to 1 MiB. No WebSocket or special routing is needed: everything is plain HTTP on one port.
API-only mode
Set [ui] dashboard = false (CRONPAD_UI__DASHBOARD=false) to serve only the API, MCP server, sign-in and health endpoints from the same binary.