Skip to content

Repository files navigation

Open CLI Deployment

A self-hostable, CLI-first PaaS for humans and coding agents.

Bring existing VPSs or let OCD provision managed capacity on Hetzner.

License: MIT Stars Docker


Open CLI Deployment (OCD) is a lightweight, self-hostable alternative to Heroku, Railway, and Render. Its CLI-first workflow is designed for both human operators and coding agents. Git supplies versioned runtime configuration; OCD checks out exact commits, builds with BuildKit, pushes immutable images to your registry, and deploys those digests to connected servers. Existing VPSs can be connected directly, while Hetzner Cloud remains an optional convenience for provisioning servers and volumes. DNS stays operator-owned and provider-neutral.

Application delivery is provider-neutral: source checkout works with HTTPS Git hosts and image publication works with compatible OCI registries such as GHCR, GitLab, Docker Hub, Quay, Harbor, or a self-hosted registry. The OCD panel image itself is currently published on GHCR, and GitHub push webhooks are the current automatic source-trigger integration.

Quick Start

The headless bootstrap below creates the panel server on Hetzner and therefore needs a Hetzner Cloud API token with Read & Write access. Normal account setup and connected-host operation do not require cloud credentials.

PANEL_IMAGE='ghcr.io/0-ai-ug/open-cli-deployment@sha256:<64-hex-digest>'
docker run --rm \
  -e HETZNER_API_TOKEN='<hetzner_token>' \
  -e OCD_AUTO_DEPLOY="{\"panel_image_ref\":\"$PANEL_IMAGE\",\"domain\":\"panel.example.com\"}" \
  "$PANEL_IMAGE"

Bootstrap provisions the server and prints its IP. Open https://<domain> and create your admin account. That's it.

Existing installation: this is a clean-cut release. Back up the OCD database and ensure every app and the panel itself have a real repository@sha256:<digest> recorded before starting the new version. Migration 105 deliberately refuses to start when any immutable artifact is missing; it never reconstructs or invents one from legacy source state.

DNS

The panel needs <domain> to resolve to the new server so Let's Encrypt can issue a TLS certificate. Bootstrap prints the exact A record to create with your DNS provider. OCD observes propagation and reports whether the record is pending, correct, or conflicting, but it never modifies or deletes DNS.

No domain? Omit domain entirely. Bootstrap derives a <server-ip>.nip.io domain once the server exists and serves it with a self-signed certificate — no DNS setup and no real domain needed (your browser will warn on first visit). Just:

PANEL_IMAGE='ghcr.io/0-ai-ug/open-cli-deployment@sha256:<64-hex-digest>'
docker run --rm \
  -e HETZNER_API_TOKEN='<hetzner_token>' \
  -e OCD_AUTO_DEPLOY="{\"panel_image_ref\":\"$PANEL_IMAGE\"}" \
  "$PANEL_IMAGE"

Prefer a config file? Copy example.panel.json to panel.json, keep the token in your shell, and run HETZNER_API_TOKEN=... bun run bootstrap. Bootstrap remembers the chosen server type and location as future capacity defaults; it never writes provider or registry secrets into panel.json.

After installing the CLI, ocd doctor reports deploy readiness. The first manifest build reuses an empty server for BuildKit or asks for browser approval to provision a dedicated worker, installs it, and resumes the deploy. Registry and private-source credentials are explicit scoped connections:

ocd registry login registry.example.com/team --username=registry-user
ocd source login git.example.com --username=git-user  # private repositories only
ocd doctor
ocd deploy

Connect an existing VPS

After logging the CLI into the panel, print OCD's enrollment key, install it for root on the VPS, independently verify the VPS's Ed25519 host-key fingerprint, then connect it:

ocd servers enrollment-key
ocd servers connect \
  --name=app-1 \
  --address=203.0.113.10 \
  --private-address=10.0.0.11 \
  --host-key='203.0.113.10 ssh-ed25519 AAAA...'

Connected VPSs are stateless app capacity. OCD never deletes them or attaches managed provider volumes; ocd servers delete app-1 only disconnects the host.

Features

  • Build exact commits from HTTPS Git hosts on dedicated OCD BuildKit workers
  • Publish to compatible OCI registries, including hosted and self-hosted options
  • Optionally trigger full reconciliation from signed GitHub push webhooks, without Actions minutes
  • Connect operator-owned stateless VPSs without cloud credentials
  • Optionally provision managed Hetzner Cloud servers, volumes, networks, and firewalls
  • Manage private Hetzner Object Storage buckets with encrypted S3 credentials
  • Automatic TLS via Traefik and Let's Encrypt HTTP-01; provider-neutral DNS instructions
  • Horizontal scaling, auto-scaling, pause/resume
  • Image-backed apps for databases, caches, and other infrastructure; reusable manifests can live in a repository catalog
  • Web terminal, log streaming, exact-image releases, promotions, and rollbacks
  • Passkeys, TOTP, GitHub OAuth, multi-user RBAC
  • ocd CLI for Linux, macOS, Windows
  • Self-managing: the panel deploys itself

PostgreSQL app recovery: clean and empty-target restore workflows. Retained volume recovery: grace-period and reattachment workflow. Hetzner S3: connection, bucket management, and safety model. Build delivery: architecture, cache, failure handling, and operator checks.

CLI-only deployments

ocd login https://panel.example.com
ocd deploy .ocd-deploy.json
ocd deploy stack ocd-stack.json
ocd logs my-app --tail=200
ocd ssh my-app -i
ocd cp my-app:/tmp/export.tar.gz ./export.tar.gz

Apps and stacks are created and configured only through versioned manifests applied by the ocd CLI. The web panel is read-only for manifest-owned app configuration and exposes operational controls such as restart, rollback, pause, wake, promotion, migration, and recovery.

Each app declares exactly one delivery source: top-level image for a prebuilt OCI reference, or build for an OCD-owned Git build. Within build, image_repository is only the destination repository for the build output; it is not a second source. ocd release --image remains the separate, artifact-only operational command.

The single-app .ocd-deploy.json schema is also used by every app entry in ocd-stack.json, so moving an app into or out of a stack does not change its deployment capabilities.

Development

bun install
bun run dev          # panel on :3001
bun run test
bun run build:cli

Built with Bun, TypeScript, React, SQLite, and Traefik.

Links

About

Open CLI Deployment — a self-hostable, CLI-first PaaS for humans and coding agents.

Resources

Contributing

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages