Skip to content
O.D.D.K./Opinionated Database Deployment Kit /PostgreSQL/MIT

Self-hosted Postgres, with the ergonomics of a managed service.

oddk runs Postgres on your Linux server: tuned instances, offsite snapshots and health alerts. One archive and one key rebuild the whole deployment.

Linux · Docker · systemd Install details

curl -fsSL https://oddk.dev/install | sh

A scuffed metal flight case stencilled O.D.D.K.

Fig. 1 — Provisioning

One command
to a live database.

Pick cores, memory and a port. oddk pulls the image and generates a password, then waits for Postgres to answer. If startup fails, it rolls back the container and volume.

Specification
Platformlinux/amd64 · linux/arm64
RuntimeDocker + systemd
Dependenciesnone (static Go binary)
Postgres13 – 18, official + compatible images
StoreSQLite · secrets AES-256-GCM
LicenceMIT
root@db01oddk create
$ oddk create --name app --version 17 \
      --port 5432 --cpu 4 --ram 8
# pulls postgres:17 if missing, then:
Created RDBMS instance: app
PostgreSQL version: 17
Port: 5432
CPU Cores: 4
RAM: 8192 MB
Status: running
Password: ••••••••••••

Connection string:
postgresql://postgres:••••@10.88.0.1:5432/postgres?sslmode=disable

Defaults sized to your instance

Memory and worker settings scale with the RAM and cores you assign. Named parameter groups let you tune further; an apply checks shared memory before replacing the container and rebuilds the previous configuration if startup fails. See the defaults and change a parameter group →

Fig. 2 — Backups

The whole deployment,
in one archive.

Every instance's data and oddk's configuration, captured on a schedule and copied offsite. Restore one instance, or rebuild the whole host. See Fig. 4.

root@db01oddk snapshot
$ oddk snapshot setup-cron --utc-hour 3 \
      --interval-hours 6 --cleanup-remote-days 30
# 03, 09, 15, 21 UTC: capture, upload, prune

$ oddk snapshot make --comment "before upgrade"
# all instances + config, verified

$ oddk snapshot list
# archive IDs, sizes and copy locations

$ oddk snapshot restore-instance \
      --instance app --id 7
# one instance put back; the rest stays up
Physical

Running instances use pg_basebackup; stopped ones use a cold file copy. Settings, ACLs and collations survive. Use --logical to cross CPU architectures.

Scheduled

Daily or every few hours: capture, upload, retry earlier failed uploads, then prune. A failed phase does not stop the phases after it.

Offsite

Your S3 bucket or S3-compatible endpoint, with a 5 GiB limit per archive. With offsite configured, failed uploads keep their local copy past retention for retry; archives over the cap age out normally.

Retained, with a floor

Local and offsite retention keep at least the newest 2 archives. The newest complete archive is kept longer.

Verified

Archives are read back in full before being catalogued, and verified again after download and before restore. A corrupt archive is refused.

Configure offsite copies and protect your master key →

Fig. 3 — Health checks & alerting

Degraded health or a failed run
sends you a message.

Health failures, failed scheduled runs and paused schedules reach you through email, Slack, Telegram or a webhook. Each message explains what broke and what it means.

Checks and thresholds

Every instance

Connection probes every 70 seconds. Missing containers fail their probes; instances in error are reported broken.

The host

Below 1 GB free disk or above 95% CPU load counts as unhealthy. Both thresholds are adjustable; the degraded alert includes host failures.

Audit

oddk checklist audits instance health, snapshot coverage, schedules, stored copies and notification configuration in one screen.

Alerts and their triggers

Degraded

After 3 consecutive failed health checks. One message per transition, so an instance parked in error alerts once rather than every 70 seconds.

Run failed

Any scheduled snapshot or backup ending with a failed phase. Names every failed phase and its recorded cause.

Recovered

A clean run after a failed one, and health returning after 2 consecutive good checks. A clean run after a clean run sends nothing.

Paused schedule

A schedule left paused by snapshot apply reminds you daily, and once at daemon start, until it is resumed or removed. See Fig. 4.

Health alerts get 3 attempts per channel. Delivery failures never fail the task being reported. Set up a channel before relying on the schedule. Configure and test notifications →

Slack · #ops · db01 ODDK Scheduled Snapshot Failed (db01)
The scheduled snapshot for the whole deployment did not complete cleanly.

  capture: the archive was written but 1 of 3 instance(s) could not be captured and hold NO database contents in it: billing (base backup: pg_basebackup failed with status 1: … No space left on device …)

AN ARCHIVE WAS PRODUCED, but it is NOT a complete capture: the instance(s) named above hold NO database contents in it and would restore as EMPTY clusters. It was still kept, uploaded and catalogued, because it remains the newest restore point for every other instance. Run 'oddk checklist' to see which instances are covered.

See 'oddk cron logs' for the full run history, or the daemon log for detail.

The body says which of these happened, because each needs a different response: no archive was produced, the archive is incomplete, or the archive is fine but could not be shipped or pruned.

Fig. 4 — Recovery procedure

Rebuilding a host
that no longer exists.

It needs the archive and the master key, and runs locally against the data directory with the daemon stopped, so it works even where the daemon cannot start.

Preflight, cheapest first

The archive, key, resources, locales and ports are checked before existing oddk state is changed.

Images pulled before anything destructive

Missing Postgres images are pulled before configuration is installed.

Clusters restored and proven

Each restored cluster must start successfully. Instances captured stopped are stopped again after verification.

Schedules restored paused

Restored schedules stay paused until you confirm this host owns its offsite destination. A rehearsal must not prune production's backups.

Follow the recovery guide →
new hostthe old one is gone
# no daemon running; apply does not need one
$ sudo -u oddk oddk snapshot apply \
      --file /mnt/restore/snapshot.tar.zst \
      --master-key /mnt/restore/master.key

# output trimmed
Preflight:
   Daemon is not running
   Target deployment is empty
   Master key decrypts instance credentials
   Host can run the recorded instance sizes
# … remaining checks; confirm to continue

Installing configuration...
   master.key installed
   oddk.db installed (2 instances)
# … clusters restored and verified

Snapshot applied.
⚠️  SCHEDULES ARE PAUSED: snapshot schedule.
# resume once this host owns its bucket:
#   oddk snapshot setup-cron --resume

Fig. 5 — Buy or run

You already have
a server.

Owned or rented, any size: oddk fits a server you operate yourself. You keep responsibility for the machine and the pager. Managed is the right answer when:

  • You need automatic failover with an SLA. oddk runs standalone instances only, and that is a scope decision that will not change.
  • Your load idles at zero, then spikes. Serverless pricing was built for that shape, and one always-on box is the wrong answer to it.
  • You need a broader platform. Database branching, application auth, storage and realtime APIs are outside oddk's scope.
  • Nobody wants to be the operator. The operating system and the hardware become yours to maintain, and paying somebody for that is a legitimate choice.

Fig. 6 — Out of scope

Deliberate limits.

If you need any of these, oddk is the wrong choice, and will stay so.

Not highly availableStandalone instances only: no failover, no streaming replicas, no quorum.
Not multi-tenantOne trusted operator, one host. Anyone with the API token has admin-equivalent control.
Not internet-facingThe API binds to loopback, Postgres to a host-local bridge. Reach them over SSH.
Not a Kubernetes operatorIt talks to the Docker API directly. On Kubernetes, use an operator.
Not a fork, driver or poolerIt orchestrates the official images. Keep your client library and PgBouncer.
Not run inside Dockeroddk is the control plane. It runs on the host and manages Docker from there.

Install with
one command.

Updating is the same command.

curl -fsSL https://oddk.dev/install | sh