Openship Integration Guide

Technology: openship · Category: hosting · Last reviewed: 2026-08-23

Source: https://tech-stack.codeamanilabs.org/guide/openship

Insight:

Openship is the self-hosted deploy tier: Apache-2.0 CI/CD that points at a repo and builds, ships, routes, and TLS-terminates it on hardware you own — a Vercel-shaped workflow without the per-seat bill or the lock-in. The trade you accept is that you are now the platform team: the OpenResty edge, Let's Encrypt renewals, Postgres, and backups are yours to keep alive. codeAmani runs a fork (codeAmani-Solutions/open-ship) as its local fleet control plane, with secrets injected by Hazina and agents wired in over MCP.

 ██████╗ ██████╗ ███████╗███╗   ██╗███████╗██╗  ██╗██╗██████╗
██╔═══██╗██╔══██╗██╔════╝████╗  ██║██╔════╝██║  ██║██║██╔══██╗
██║   ██║██████╔╝█████╗  ██╔██╗ ██║███████╗███████║██║██████╔╝
██║   ██║██╔═══╝ ██╔══╝  ██║╚██╗██║╚════██║██╔══██║██║██╔═══╝
╚██████╔╝██║     ███████╗██║ ╚████║███████║██║  ██║██║██║
 ╚═════╝ ╚═╝     ╚══════╝╚═╝  ╚═══╝╚══════╝╚═╝  ╚═╝╚═╝╚═╝

Openship Integration Guide

Focus: Running codeAmani's own deploy platform — install the control plane, deploy a repo end to end, and operate the local fork (codeAmani-Solutions/open-ship) that fronts the fleet.

Overview

Openship is an open-source, self-hostable deployment platform with built-in CI/CD, licensed Apache-2.0. Point it at a GitHub repo, a folder on disk, or a prebuilt artifact and it runs one pipeline end to end: detect the stack, build an image, run it on loopback, then write an OpenResty reverse-proxy vhost and issue a Let's Encrypt certificate for your domain. Push-to-deploy, preview environments, rollbacks, managed Postgres/MySQL/MongoDB/Redis, a built-in SMTP engine, and backups all live in the same control plane.

Three interfaces drive the same backend: a desktop app (Electron), a web dashboard (Next.js), and a CLI (npm i -g openship) — plus a REST API and an MCP endpoint so agents can drive it.

⚠️ Name collision. There is an unrelated project openshiporg/openship about e-commerce order fulfilment. This guide is oblien/openship — the deployment platform at https://openship.io. The npm package openship is the deployment CLI.

Where it sits next to the managed hosts

Vercel Netlify Render Cloudflare Openship
Model Managed serverless Managed serverless Managed containers Edge Workers Self-hosted containers
Who runs the machine Vercel Netlify Render Cloudflare You
Long-running processes ✗ (functions) ✗ (functions) ✓ ✗ (isolates) ✓
Cost at rest Per seat / usage Per seat / usage Per service Per request VPS rent only
Egress Metered Metered Metered R2 = free Your provider's
Data residency Their regions Their regions Their regions Their edge Wherever you rack it
TLS / routing Automatic Automatic Automatic Automatic OpenResty + certbot, you own the renewal

Reach for Openship when the project must keep infra in-house, when a single €5–€20 VPS has to carry several apps that would each be a paid Render service, or when the same box must also host Postgres, Redis, and a mail engine. Stay on Vercel (codeAmani's primary host) for the marketing site, the dashboard, and anything where a git push to master shipping itself is worth more than owning the box.

flowchart LR
  A["Source<br/>GitHub repo · local folder · artifact"] --> B["Detect<br/>package.json · lockfile · openship.json"]
  B --> C["Build<br/>Docker image or bare release<br/>config frozen into a snapshot"]
  C --> D["Run<br/>container on loopback only<br/>never a public port"]
  D --> E["Route + secure<br/>OpenResty vhost + Let's Encrypt HTTP-01"]
  E --> F["Live domain<br/>https://app.example.com"]
  G["git push"] -.->|"webhook"| B
  H["CLI · dashboard · desktop · MCP"] --> I["Control plane API<br/>Hono + Postgres/PGlite"]
  I --> B

Official Documentation

Resource URL
Docs home https://openship.io/docs
Quickstart https://openship.io/docs/getting-started/quickstart
Installation https://openship.io/docs/getting-started/installation
Core concepts https://openship.io/docs/getting-started/core-concepts
First deployment https://openship.io/docs/getting-started/first-deployment
Architecture overview https://openship.io/docs/architecture/overview
Deploy from GitHub https://openship.io/docs/guides/deploy-from-github
CLI reference https://openship.io/docs/cli
API reference https://openship.io/docs/api
MCP endpoint https://openship.io/docs/mcp
Upstream repo https://github.com/oblien/openship
Pricing / Cloud https://openship.io/pricing

Install

The install script brings its own Node when the system one is older than 22; the package-manager install runs on the Node you already have.

# macOS / Linux — fastest path (bundles Node)
curl -fsSL https://get.openship.io | sh

# or via a package manager (needs Node 22+)
npm i -g openship
# Windows (docs' one-liner — verify it resolves before trusting it; see NOTES.md)
irm https://git.openship.io/windows | iex

Then run the wizard — it creates the first admin, wires your domain, and installs Openship as a boot service:

openship            # guided setup, then the control panel
openship open       # opens the dashboard (default http://localhost:3001)
openship status     # health
openship doctor     # diagnose a broken install

For CI and headless boxes, skip the wizard:

openship up                                             # install + start as a background service
openship up --public-url https://openship.example.com   # + serve the dashboard on your domain (edge + TLS)
openship up --foreground                                # attached to the terminal
openship stop
openship update

Which mode openship up picks

Host Mode What you get
Linux with Docker Compose (default, force with --compose) Full stack from published ghcr.io/oblien/* images — Postgres, Redis, API, dashboard, and a containerized OpenResty edge on :80/:443. This is the flavour that hosts your deployed apps on the same box.
macOS / Windows / Linux without Docker bare (force with --bare) One lightweight process with an embedded database. An always-on control plane that deploys out to a server over SSH or to Openship Cloud.

Default ports: API :4000, dashboard :3001, edge :80/:443 in Compose mode.

Raw Docker Compose (no CLI)

The self-hosted stack lives in docker/docker-compose.yml and pulls published images — no monorepo compile.

git clone https://github.com/oblien/openship.git && cd openship
cp .env.example .env          # then edit
docker compose --env-file .env -f docker/docker-compose.yml up -d

Linux only (the edge uses network_mode: host). The api container mounts the host Docker socket so the control plane can build and run your apps as host containers — that is host-privileged through the socket, so run it only on a trusted host.

The root docker-compose.yml is a different file — it is the from-source control plane (builds from source, ships the marketing site, no edge, no socket). It does not self-host your apps.


Deploy a project

cd your-project
openship init            # link this directory to a project
openship deploy
openship deploy --watch  # follow build logs
openship logs

Or from the dashboard: Library → Repositories, pick the repo, choose the target (Local / Your Server / Cloud), review the detected framework + build command + domain, press Deploy, and watch the logs stream.

Turn on CI:

openship project create --name my-app --git-owner codeAmani-Solutions --git-repo my-app
openship project git auto-deploy my-app --enable

A GitHub webhook then re-runs the pipeline on every push to the tracked branch — rebuilding only the services a monorepo push actually touched.

Push-to-deploy and public domains need an always-on server or Cloud. A desktop/loopback instance has no public endpoint for GitHub to call.

Detected stacks

Node, Python, Go, Rust, PHP, Ruby, Java, .NET, plain Docker images, Docker Compose files deployed as-is, and monorepos. Zero config files are required; an openship.json in the repo overrides the guesses when you want control.


CLI map

Group Commands
Run / update the instance up, stop, update, install, open, status, doctor, reset-admin-password
Deploy init, deploy, deployment, logs
Resources project, service, domain
Access & API login, logout, context, token, api
Edge & monitoring edge
Self-host infra server, system, mail, backup

Global flags: --json (machine-readable — the one to use from scripts and agents), --version, --help.

Auth uses personal access tokens prefixed opsh_pat_:

openship login                                 # interactive
openship login --token opsh_pat_xxxxxxxxxxxx   # non-interactive / CI

Multiple instances are named contexts, stored in ~/.openship/config.json:

openship context            # list
openship context use prod   # switch

Shell completion — the static file is the fast option (regenerate after upgrades):

openship completion bash > /etc/bash_completion.d/openship
openship completion zsh  > ~/.zsh/completions/_openship
openship completion fish > ~/.config/fish/completions/openship.fish

Architecture

flowchart TD
  subgraph UI["Interfaces"]
    D1["Dashboard · Next.js"]
    D2["CLI · npm openship"]
    D3["Desktop · Electron"]
    D4["MCP · /api/mcp"]
  end
  UI -->|"HTTP /api/*"| API["Control plane API · Hono<br/>projects · deployments · domains<br/>env vars · backups · permissions"]
  API --> DB[("Postgres + Drizzle<br/>or embedded PGlite")]
  API -->|"getPlatform()"| ADP["@repo/adapters"]
  ADP --> R["runtime<br/>Docker · bare process · cloud"]
  ADP --> I2["infra<br/>OpenResty edge · certbot"]
  ADP --> S["system<br/>docker/git prereq checks"]
  R --> T{"Target"}
  T --> T1["Local machine"]
  T --> T2["Your server over SSH"]
  T --> T3["Openship Cloud"]

Core concepts

Term Meaning
Project One app. Remembers where the code comes from, how to build it, and everything attached.
Service A piece of a multi-part project — website, database, cache — running side by side.
Deployment One attempt to build and publish. Successful releases get version numbers (v1, v2…).
Environment A separate copy of the project: Production or Preview.
Domain The address people type, e.g. app.example.com.
Runtime / target Where the app actually runs: Local, Your Server (SSH), or Openship Cloud.

Driving Openship from Claude Code (MCP)

Openship exposes a single MCP endpoint at POST /api/mcp. Only routes that opt in become tools, every call re-runs the full auth and permission stack, and credential/token routes can never become tools.

# OAuth 2.1 — browser consent on first use (recommended)
claude mcp add --transport http --scope user openship https://openship.example.com/api/mcp

Static-token fallback:

claude mcp add --transport http openship https://openship.example.com/api/mcp \
  --header "Authorization: Bearer opsh_pat_…"
{
  "mcpServers": {
    "openship": {
      "url": "https://openship.example.com/api/mcp",
      "headers": { "Authorization": "Bearer opsh_pat_…" }
    }
  }
}

Tools map onto REST routes — get_projects, get_deployments, post_deployments_build_access, get_domains, post_domains, get_github_repos, get_analytics, get_jobs, post_jobs_by_key_run. Call tools/list to see exactly what your token can reach; a read-only token yields only read tools.

Use the public HTTPS origin, not localhost, so OAuth discovery and consent resolve in the browser.


codeAmani's fork — the local fleet control plane

codeAmani does not run stock Openship. The working checkout lives in WSL Ubuntu at ~/projects/grok-projects/open-ship:

origin https://github.com/codeAmani-Solutions/open-ship.git
upstream https://github.com/oblien/openship.git (merged in; currently v0.6.5)
Shape Bun 1.3 + Turbo monorepo — apps/{api,cli,dashboard,desktop,edge,email,web}, packages/{adapters,core,db,db-email,onboarding,ui}
License Apache-2.0 (unchanged)

Upstream's bun dev ports (:4000 / :3001) are not what this machine runs — 127.0.0.1:4000 is reserved for the globally installed openship CLI. The fleet topology is supplied by docker-compose.override.yml:

Surface Host port URL
Dashboard 3246 http://127.0.0.1:3246
API 3247 http://127.0.0.1:3247/api/health
Marketing web 3236 http://127.0.0.1:3236
Deploy-agent SSH 2222 ssh -p 2222 -i ssh-keys/id_ed25519 root@127.0.0.1
Webmail 20000 http://127.0.0.1:20000
Public dashboard Cloudflare tunnel https://open-ship.codeamani.com → :3246
Public API Cloudflare tunnel https://open-ship-api.codeamani.com → :3247

Bring-up (from the repo root, inside WSL):

hazina inject open-ship --check    # names only — never values
hazina inject open-ship            # writes .env.local

docker compose -f docker-compose.yml -f docker-compose.override.yml up -d
node scripts/dev-status.mjs        # host + merged product issues

What the fork adds

1. deploy-agent/ — an SSH target the control plane can actually reach. When the API runs in Compose it cannot SSH to the WSL host (no sudo, no host sshd), so the fork ships an Ubuntu 24.04 container that is the deploy target: openssh-server with PasswordAuthentication no / PermitRootLogin prohibit-password, plus docker-ce-cli, docker-compose-plugin, and — critically — docker-buildx-plugin, because Openship runs docker build --progress=plain and the legacy builder rejects --progress with exit 125. It runs network_mode: host and mounts the host Docker socket, /var/lib/openship, and /etc/letsencrypt — that last bind is a fork fix: certsExist() inspects the deploy-agent's filesystem over SSH, so without it SSL verification cannot see certificates the edge already issued.

2. A product-issue operator loop. Containers up ≠ platform healthy — Openship has its own outage feed, so the fork wraps it in scripts:

node scripts/openship-api.mjs issues    # session-cookie API client
node scripts/dev-status.mjs             # host state + merged product issues
node scripts/dev-status.mjs --strict    # non-zero exit if product outages exist
node scripts/fleet-reconcile.mjs        # recycle the SSH pool + rebind the webmail container
node scripts/ops-watch.mjs --once       # DONE / FAILED

fleet-reconcile.mjs exists because after a deploy-agent recreate the API's SSH pool stays pinned to the dead connection and GET /issues reports the agent unreachable even though ssh -p 2222 works. Reconcile PATCHes the server to invalidate the pool and rewrites deployment.container_id for the host-network webmail.

3. Agent MCP over subscription logins. The fork documents connecting Grok Build and Claude Code to the instance over Openship's OAuth — using grok login / claude login subscriptions, not XAI_API_KEY or ANTHROPIC_API_KEY:

claude mcp add --transport http --scope user openship \
  https://open-ship.codeamani.com/api/mcp

grok mcp add --transport http openship \
  https://open-ship.codeamani.com/api/mcp

OAuth resource/issuer is the public dashboard origin. A PAT (Authorization: Bearer ${OPENSHIP_MCP_TOKEN}) is the local fallback — and that is an Openship token, not an xAI or Anthropic key. Never hand-edit ~/.claude.json; use claude mcp add.

4. Webmail / apps/email work. Mailbox identity (display names, colours, BIMI-style brand avatars), a single CID-embedded HQ signature image, an Outlook-shaped settings shell (Mail + Accounts), a richer compose toolbar, PWA offline support, and OpenPGP passphrase encryption on send (apps/email/server/src/lib/encrypt-pgp.ts). The mail engine and its database are not in the compose file — Openship installed them as managed containers.

Secrets: Hazina, not .env by hand

Every secret for this checkout flows through the Hazina vault project open-ship. Two files, deliberately not collapsed:

File Owner Consumed by
.env Compose stack docker compose env_file for api / dashboard
.env.local hazina inject open-ship scripts/auto-login-local.mjs, operator tooling

Do not env_file: .env.local into compose — mailbox passwords must never enter the API container. Bound names include OPENSHIP_ADMIN_EMAIL, OPENSHIP_ADMIN_PASSWORD, BETTER_AUTH_SECRET, INTERNAL_TOKEN, POSTGRES_PASSWORD, CLOUDFLARE_TUNNEL_TOKEN, PORKBUN_API_KEY / PORKBUN_SECRET_KEY, RESEND_API_KEY, and OPENSHIP_MCP_TOKEN. Values are revealed only on the operator machine (hazina get open-ship/… --reveal) and never printed in chat. See docs/HAZINA.md and docs/DEV-WORKFLOW.md in the fork.


Environment variables

Self-hosting keys read from .env (see .env.example in the repo):

OPENSHIP_VERSION=            # pin a release for reproducible image pulls
OPENSHIP_PUBLIC_URL=         # public origin the dashboard/API are served on
OPENSHIP_BIND_ADDR=127.0.0.1 # which interface published ports bind to
TRUST_PROXY=                 # set when a reverse proxy / tunnel fronts the stack
API_PORT=4000
DASHBOARD_PORT=3001
POSTGRES_PASSWORD=
BETTER_AUTH_SECRET=
INTERNAL_TOKEN=
GITHUB_CLIENT_ID=            # optional: GitHub OAuth device flow on self-hosted

CLI-side, an opsh_pat_… token authenticates openship login --token and the MCP header.


codeAmani notes


Troubleshooting

Issue Fix
openship open fails / API not responding openship up, wait, retry. openship doctor for a full diagnosis.
Build failed Read the last red lines of the build log — usually a missing build command, an unset env var, or a wrong port.
Host operations hang (:80/:443 takeover, mail engine, host terminal) The container→host SSH channel is missing. openship up provisions it; raw Compose does not — the five manual steps are in .env.example under Host operations from the container. See https://openship.io/docs/troubleshooting/host-channel
docker build exits 125 on a custom deploy target Openship passes --progress=plain; the legacy builder rejects it. Install docker-buildx-plugin.
SSL verify says "no certs" although the edge issued them certsExist() reads the deploy target's filesystem — bind-mount the host /etc/letsencrypt into it.
Agent unreachable after recreating the deploy target The API's SSH pool is pinned to the dead connection. Reconcile the server record (fork: node scripts/fleet-reconcile.mjs).
Dashboard /api/health 404 The dashboard has no health route — probe /login returning 200 instead.
openship update refuses to touch a raw-Compose stack update only reconciles a stack the CLI installed. Pin OPENSHIP_VERSION and run docker compose … pull && … up -d.
Public dashboard 502 behind a tunnel Check the tunnel origins point at the real dashboard/API ports, not the CLI's :4000.
npm i -g openship fails on the Node version The CLI needs Node 22+. Use the get.openship.io install script instead — it bundles its own Node.

Official docs: