voodu apply, no git pushSelf-hosted PaaS.
Commitless deploys.
Voodu is a Heroku-shaped, Kubernetes-honest deploy tool you run on your own boxes. One HCL file. One voodu apply. No git push, no bare repo, no plugin sprawl — apps, ingress with TLS, and stateful services that actually back themselves up.
One file describes the running system.
HCL out-of-the-box. No YAML to coerce, no Compose tricks, no chart-of-charts. Apps, ingress, databases, jobs — every kind shares the same shape and the same blast radius.
// Production stack: HA postgres + HA redis behind the app.
app "myorg" "web" {
image = "ghcr.io/myorg/web:latest"
replicas = 3
host = "app.example.com"
env = {
DATABASE_URL = "postgres://myorg/db"
REDIS_URL = "redis://myorg/cache"
}
tls { email = "ops@example.com" }
}
postgres "myorg" "db" {
image = "postgres:16"
replicas = 3 // 1 primary + 2 standbys
database = "myapp"
}
redis "myorg" "cache" {
replicas = 3
}
redis "myorg" "cache-ha" {
sentinel { monitor = "myorg/cache" }
}scope + name, every kind
Every resource is keyed by (scope, name). Scope groups apps, envs or teams; name is unique inside it. The same key flows through diff, apply, and prune.
Databases ship as first-class kinds
Postgres with replicas + nightly backup, Redis with Sentinel — declared in the same file as your app. Mongo, RabbitMQ, Kafka land via the plugin model; /opt/voodu/plugins is where new kinds appear.
TLS, health, ingress — no chart
Add tls{} and voodu cuts a Let's Encrypt cert via Caddy. host wires ingress. No Helm, no sidecars, no YAML blast radius.
No accidental deletions
apply only creates and updates. Resources missing from the file stay put. --prune is the explicit opt-in, scoped per (scope, kind)so siblings of other types aren't touched.
Laptop talks. Server reconciles.
The CLI streams your build context over SSH. The controller diffs it against embedded etcd, builds, swaps, and reroutes. It's k8s-shaped reconciliation without the k8s-shaped operations bill.
your laptop your server ─────────── ─────────── $ voodu apply -f voodu.hcl ──ssh──▶ voodu-controller │ │ │ └─ reconcile ingress / services (etcd) │ (build mode only: stream tarball) └─ tar -czf - <path> ──ssh──▶ voodu receive-pack └─ extract → build image → swap `current` symlink → run post_deploy hooks → recreate container
Write the manifest
One HCL file, or many. Apps, ingresses, databases, jobs — pick what you need, ignore the rest.
# voodu.hcl app "prod" "api" { ... } ingress "prod" "api" { ... }
See the plan first
voodu diff dry-runs the apply: what's new, changed, pruned. Wire --detailed-exitcode in CI to gate deploys.
$ voodu diff -f voodu.hcl
~ deployment/prod/api
~ replicas 1 → 3
+ redis/clowk-lp/redis-haShip over SSH
Same flags, no surprises. Tarball is content-addressed — identical trees skip the rebuild and just repoint current.
$ voodu apply -f voodu.hcl -r prod-1
✓ apply complete in 11.8s
✓ 3/3 healthyA small, k8s-shaped command surface.
Six verbs cover ~95% of day-to-day. Everything else is a plugin discovered from /opt/voodu/plugins.
$voodu remote setup prod-1 ubuntu@hostSSH preflight + install + server-mode setup. Idempotent.
$voodu apps create prodSeeds /opt/voodu/apps/prod with a fresh .env.
$voodu apply -f voodu -r prod-1Streams context over SSH, reconciles, swaps containers.
$voodu diff -f voodu.hcl --detailed-exitcodePlan-style output. Exit 2 means changes pending.
$voodu config set DATABASE_URL=… -a prodOut-of-band secrets. Always wins over manifest env blocks.
$voodu plugins:install thadeu/voodu-postgresDrops a binary into /opt/voodu/plugins. Voodu wires it up.
Declared. Observed. Operated.
The CLI that ships resources is the CLI that watches them. Postgres replicas, Redis Sentinel state, job histories — all visible from one place, without a second portal.
Apply, and it exists.
Postgres replicas, Redis Sentinel, app behind TLS — every kind shipped by one apply. No portals, no chart-of-charts.
See it. Get notified.
Live CPU + memory via vd stats. Per-probe push notifications via on_probe — Telegram, Slack, PagerDuty fire the moment a pod transitions. No agent, no Grafana, no portal in the middle.
One CLI, every day.
Stream logs across replicas, rollback by release ID, exec into any container. vd is the only surface — no SSH gymnastics, no portal hand-offs.
Built on boring, well-loved parts.
Voodu is opinionated where it counts and pluggable where you'd want it to be. Postgres, Mongo, Redis, ingress — independent binaries, not a rewritten universe.
Go controller
Single static binary, embedded etcd, HTTP API on 127.0.0.1:8686.
Caddy ingress
Replaces NGINX. Caddy Admin API, ACME, on-demand wildcard TLS.
Docker builds
Bring a Dockerfile or let voodu auto-detect Go, Ruby, Rails, Python, Node.
SSH transport
Tarball over SSH. No daemons listening on the public internet.
voodu-postgres
Postgres with backup, replica and test-restore. Declared like an app.
voodu-redis
Redis service with the same reliability primitives.
voodu-caddy
Reference plugin. Read it to write your own.
Build your own
Independent binaries discovered from /opt/voodu/plugins. Install via GitHub repo.
Questions devs actually ask.
Why HCL and not YAML?+
kind/scope/name. Voodu is HCL-only — no YAML coercion, no indentation traps.How is this different from Dokku, Coolify, CapRover?+
diff shows the plan, apply reconciles. Stateful services are first-class, not bolted on as plugins-of-plugins.Is it really commitless?+
voodu apply tars whatever directory you're in and streams it over SSH. No bare repo, no git push, no commit required. Edit, save, apply.What about K8s?+
Does it scale beyond one host?+
voodu apply -f voodu.hcl -r $r. Same manifest, different SSH targets. The controller is per-host and stateless about its peers — keep it boring.How do secrets work?+
voodu config set stores env vars per-app, out-of-band from the manifest. config:set always wins over envblocks, so a runaway apply can't reset a production secret.