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

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.
$ 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.
$ 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
Running instances use pg_basebackup; stopped ones use a cold file copy.
Settings, ACLs and collations survive. Use --logical to cross CPU architectures.
Daily or every few hours: capture, upload, retry earlier failed uploads, then prune. A failed phase does not stop the phases after it.
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.
Local and offsite retention keep at least the newest 2 archives. The newest complete archive is kept longer.
Archives are read back in full before being catalogued, and verified again after download and before restore. A corrupt archive is refused.
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
Connection probes every 70 seconds. Missing containers fail their probes;
instances in error are reported broken.
Below 1 GB free disk or above 95% CPU load counts as unhealthy. Both thresholds are adjustable; the degraded alert includes host failures.
oddk checklist audits instance health, snapshot coverage, schedules,
stored copies and notification configuration in one screen.
Alerts and their triggers
After 3 consecutive failed health checks. One message per transition, so an
instance parked in error alerts once rather than every 70 seconds.
Any scheduled snapshot or backup ending with a failed phase. Names every failed phase and its recorded cause.
A clean run after a failed one, and health returning after 2 consecutive good checks. A clean run after a clean run sends nothing.
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 →
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.
# 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.
Install with
one command.
Updating is the same command.
curl -fsSL https://oddk.dev/install | sh