Bundar.dev - push-to-deploy on your own servers

Hi all, I want to show off a project I’ve been building: Bundar.

What it is: a push-to-deploy platform. You bring your own server (any VPS), connect it, and then:

git push bundar main
→ container image built
→ health checks
→ live, zero downtime

Push-to-deploy, health-checked deploys, automatic HTTPS. You can try it for free — point it at a VPS and push.

Why it’s an Elixir story. The whole control plane is a Phoenix app, and Elixir is doing the heavy lifting:

  • Phoenix LiveView — the entire dashboard. Deploy logs, server status, service config, all live.
  • Postgres — the source of truth for servers, services, and deploy state.
  • Oban (free version) — the deploy and provisioning pipeline. One deploy fans out into Oban jobs: build the image, roll it out, run health checks, clean up. Server onboarding is jobs too (install Podman, configure firewalld, set up the reverse proxy, configure sshguard), plus a scheduled ping job that watches every server’s health.
  • PUI — our own LiveView UI toolkit, extracted from building this dashboard and open-sourced on Hex (MIT): pui | Hex . The whole UI is built on it.
  • NimblePublisher — powers the blog (bundar.dev/blog)

A couple of implementation details that might interest folks here:

  • SSH client, forked from librarian. The management plane is SSH hub-and-spoke — the control plane talks to customer servers over Erlang’s :ssh. The client started as librarian, which we forked and later ported in-app (SSHClient) so we could evolve it alongside Bundar: streaming command output as an Enumerable, connection/identity handling, and SSH tunneling, which we use when setting up the reverse proxy on a fresh server.
  • Remote curl as a Req adapter. Some HTTP calls need to happen on the server itself (registries, local endpoints), not from the control plane. So we wrote a custom Req adapter: the app builds a request with the normal Req API, and the adapter translates it into a curl command, executes it remotely over SSH, then parses the raw curl -i output back into a %Req.Response{}. The rest of the codebase never knows the request physically ran somewhere else.
  • Containers run under Podman with --userns=auto, so container root maps to an unprivileged host UID. We’re writing about the Podman isolation model on our blog: https://bundar.dev/blog
  • Caddy for reverse proxy / load balancer. Each server runs a Caddy container (bundar_proxy) provisioned by an Oban job, and routes are configured dynamically through Caddy’s admin API — which is exactly what the remote-curl Req adapter talks to, over the SSH tunnel.

A note on how it was built. I started learning Elixir & Phoenix in April 2023, and this side project began around October 2023 — before agentic coding was a thing. I bought the bundar.run domain in February 2024 (it expired in February 2026, so now we’re on bundar.dev). These days AI agents help me move a lot faster, but I still review and test everything manually.

One question for the forum. No plans to open-source Bundar for now, but self-hosting is a real possibility down the road. I’d love advice on the best way to ship a compiled release with feature flags — e.g. a self-hosted build with the billing, blog, and landing page modules stripped out. Application env + compile-time config? Runtime feature flags? Something else entirely? Curious how others have done this with mix release.

I’d love feedback — especially from anyone running Oban pipelines in production, or building LiveView dashboards. What would you want to see next? Happy to answer questions about the stack.

Thanks for reading!

4 Likes