In partnership with

You are paying for a VPS to run your deployment tool so it can run your app.

OpenShip disagrees with that architecture at a fundamental level.

SnackOnAI Engineering | Senior AI Systems Researcher | Technical Deep Dive | July 23, 2026

What It Actually Does

OpenShip (oblien/openship, AGPL-3.0, 2.9k stars) is a self-hosted deployment platform built on a single design principle: your production server runs your apps and nothing else. Builds happen on your local machine or in the cloud. The artifact, a versioned OCI container image, streams to the target server over SSH. The server receives a container and starts it. That is the entire protocol.

There is no agent on your server. No dashboard process competing for RAM with your Node app. No build queue eating your I/O budget. The control plane lives on your laptop, in your browser, or in a desktop app. The server is a dumb container host.

This is not a minor implementation detail. It changes the economics, the failure modes, and the security surface area of running your own PaaS.

The Architecture, Unpacked

Caption: The key insight is the gap between the two boxes. Everything that consumes resources for operations lives in the top box. Your server only receives finished containers.

The Code, Annotated

Snippet One: The .env Mode Switch

OpenShip's entire self-hosted vs. SaaS distinction is controlled by two environment variables. One compose stack, one set of services, behavior decided entirely at runtime:

# .env — the only knob you turn
# Source: https://github.com/oblien/openship/blob/main/.env.example

# ─── THE SINGLE SWITCH ──────────────────────────────────────────
# false = self-hosted (default, no billing, no GitHub App required)
# true  = SaaS mode (billing, metering, multi-tenant, GitHub App REQUIRED)
CLOUD_MODE=false   # ← THIS is the architectural fork

# Runtime target: local | docker | cloud-saas
DEPLOY_MODE=docker

# ─── Storage ────────────────────────────────────────────────────
DATABASE_URL=postgresql://openship:openship@localhost:5432/openship
# Note: compose OVERRIDES this with internal DNS (postgres:5432)
# The value here is for running apps OUTSIDE Docker (dev only)

# ─── Auth (Better Auth) ─────────────────────────────────────────
BETTER_AUTH_SECRET=change-me-in-production
# REQUIRED: API REFUSES to boot with the default value in any non-dev target

INTERNAL_TOKEN=change-me-32-byte-random-hex
# ← THIS is the internal auth token fronting trusted API endpoints
# The API hard-refuses to start without a non-default value
# This protects the internal API surface from external callers

# ─── Redis ──────────────────────────────────────────────────────
OPENSHIP_REQUIRE_REDIS=true
# Disables the silent in-memory fallback.
# Without this, a Redis failure silently degrades to an in-memory queue.
# Set TRUE in any multi-replica self-hosted deployment.

# ─── SaaS-only (CLOUD_MODE=true) ────────────────────────────────
# GITHUB_APP_ID=
# GITHUB_PRIVATE_KEY_BASE64=    # base64 of .pem key
# OBLIEN_CLIENT_ID=             # cloud billing / metering
# OBLIEN_WEBHOOK_SECRET=        # HMAC for /api/billing webhook (else 503)

Caption: The CLOUD_MODE flag is the architectural boundary. One codebase, two runtime personalities. The API enforces the correct secret requirements at boot time, so misconfigured deploys fail fast.

Snippet Two: The Docker Compose Control Plane

The compose file reveals the deliberate isolation between the control plane and the compute layer:

# docker-compose.yml (annotated)
# Source: https://github.com/oblien/openship/blob/main/docker-compose.yml

services:

  # ─── Storage: PRIVATE, never exposed to host ────────────────
  postgres:
    image: postgres:16-alpine
    expose:
      - "5432"   # ← expose, not ports. Only reachable inside compose network.
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-openship}"]
      interval: 5s
      retries: 12   # ← 12 retries before giving up, giving Postgres ~60s to start

  redis:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes"]  # ← persistence on
    expose:
      - "6379"   # ← same: private to compose network

  # ─── API: the control plane ─────────────────────────────────
  api:
    build:
      context: .
      dockerfile: apps/api/Dockerfile
    ports:
      - "4000:4000"
    env_file: .env
    environment:
      DATABASE_URL: postgresql://...@postgres:5432/openship  # ← compose DNS wins
      REDIS_URL: redis://redis:6379                           # ← compose DNS wins
    depends_on:
      postgres:
        condition: service_healthy   # ← waits for BOTH healthchecks, not just startup
      redis:
        condition: service_healthy
    healthcheck:
      # Uses bun to send an HTTP check — no curl dependency in the image
      test: ["CMD-SHELL", "bun -e \"fetch('http://127.0.0.1:4000/api/health').then(r=>process.exit(r.ok?0:1))\""]
      start_period: 40s   # ← 40s grace for DB migrations on first boot

  # ─── Dashboard: depends on healthy API ──────────────────────
  dashboard:
    environment:
      INTERNAL_API_URL: http://api:4000  # ← internal DNS, not a public URL
    depends_on:
      api:
        condition: service_healthy   # ← ← THIS is the boot ordering contract

Caption: Postgres and Redis use expose, not ports. The entire storage layer is invisible to the host network. The healthcheck dependency chain (postgres healthy → redis healthy → api healthy → dashboard starts) means a first boot with a slow DB migration cannot produce a broken dashboard state.

It In Action: End-to-End Deployment

Scenario: Deploy a Next.js app on a fresh $6/mo Hetzner VPS.

Step 1: Install and init (local machine)

npm i -g openship        # ~15 seconds
openship init            # prompts for target server SSH credentials
                         # Output: "Connected to 65.21.x.x (Hetzner, 2vCPU/4GB)"
                         # Output: "OpenResty configured, Let's Encrypt ready"

Step 2: Connect the repo

cd my-nextjs-app
openship deploy
# Output:
# Detected: Next.js 15, Node 22
# Building image locally...   [====================] 2m 14s
# Image: openship/my-nextjs-app:sha-a3f9c2b (487MB)
# Streaming to 65.21.x.x over SSH...  [===========] 34s
# Starting container on private network...
# Wiring domain: app.example.com → container (port 3000)
# Issuing Let's Encrypt cert...  done (6s)
# Zero-downtime swap complete.
# Live: https://app.example.com  [deploy took 3m 4s total]

Step 3: What the server actually runs

# SSH into the VPS and check what's running
ssh [email protected] docker ps
# CONTAINER         IMAGE                              CPU    MEM
# my-nextjs-app     openship/my-nextjs-app:sha-a3f9c2b  0.2%   180MB
# openresty         openresty/openresty:alpine          0.1%    18MB
#
# Total: 2 containers, ~200MB RAM, ~0.3% CPU
# Coolify comparison: 8-12 containers, ~800MB-1.2GB RAM for the platform alone

Step 4: Instant rollback

openship rollback
# Output: Reverted to sha-9d1b4e7 in 8 seconds. Zero downtime.

Numbers that matter:

  • Build time (Next.js, local M3 MacBook Pro): ~2 min 14s

  • Image stream to Hetzner EU: ~34s for 487MB

  • Zero-downtime swap: ~8s

  • Let's Encrypt cert issuance: ~6s

  • Server memory overhead from platform: ~18MB (just OpenResty)

Why This Design Works (and What It Trades Away)

The local-build architecture solves a real problem most self-hosted PaaS tools ignore: the build process and the runtime process compete for the same resources on a single server. When you push a large build on Coolify or CapRover, your production app shares CPU with the CI runner, shares memory with the build cache, shares I/O with the npm install process. OpenShip moves that entire workload off the server.

The tradeoff is latency and network cost. A large image has to travel from your machine to the server on every deploy. If your connection is slow or your image is large, that 34-second stream in the example above becomes minutes. The repo acknowledges this and provides the option to run builds on the server as a config flag, but the default and the design intent is local builds.

The single-flag mode switch (CLOUD_MODE) is a genuinely elegant architecture decision. One codebase, one compose stack, two runtime personalities. The SaaS mode activates GitHub App authentication (token and CLI auth are hard-rejected at boot), Oblien billing metering, and multi-tenant workspace isolation. This means the codebase you self-host is exactly the codebase running the cloud product. There is no "lite" version. What diverges is purely runtime configuration.

OpenResty as the reverse proxy is a deliberate choice over Nginx or Caddy. OpenResty is Nginx with LuaJIT embedded, allowing programmable request routing, rate limiting logic, and cache invalidation all in-process without spawning external services. The zero-downtime swap happens inside OpenResty's Lua layer, not by restarting the proxy.

The MCP (Model Context Protocol) server integration is the forward-looking piece. Openship exposes a standard MCP endpoint, meaning Claude, Cursor, or any MCP client can issue deploys, check logs, and trigger rollbacks via natural language commands. This is infrastructure-as-agentic-tool, not just infrastructure-as-code.

Technical Moats

The control plane architecture is hard to bolt on. Tools like Coolify were designed with the control plane on the server from day one. Retrofitting that to a local-first, SSH-streaming model requires rethinking the entire job queue, the build pipeline, and the networking assumptions. Starting fresh with that constraint, as OpenShip does, is easier than migrating to it.

The Turborepo monorepo structure with Bun produces a workspace where the API, dashboard, web, and shared packages all build with a single command and can be independently containerized. The turbo.json task graph ensures packages build in dependency order automatically. This is not novel, but it is well-executed: the dev:saas task variant allows the team to run the full SaaS mode locally against real Oblien billing infrastructure, catching mode-specific bugs in development.

One compose stack for both modes means every self-hosted deployment is automatically validating the same infrastructure the SaaS product runs on. Bugs that only appear in multi-tenant mode still get caught by self-hosters who flip CLOUD_MODE=true experimentally.

Contrarian Insights

Insight One: The "zero config" claim has a hidden ceiling. Auto-detecting Next.js from a push is straightforward. Auto-detecting a monorepo with a custom build target, a non-standard output directory, or a framework OpenShip has not seen before requires fallback to Docker, and then "zero config" becomes "write your Dockerfile." Every PaaS with auto-detection has this ceiling. OpenShip is honest that Docker compose files deploy as-is, which is the right escape hatch, but the ceiling exists and teams will hit it. The question is whether you hit it at project three or project thirty.

Insight Two: The MCP integration is the actual product moat, not the deployment platform. Heroku, Render, Fly, Coolify, CapRover: this is a crowded space. "Builds locally, ships containers" is a meaningful differentiator today but is reproducible by a determined team. An MCP server that lets AI agents autonomously deploy, rollback, scale, and monitor production infrastructure is not reproducible with a weekend project. OpenShip is positioning itself as the deployment layer for agentic workflows, not just a PaaS. That is a much less crowded market.

Surprising Takeaway

The API hard-refuses to boot if INTERNAL_TOKEN equals the example placeholder value. This is not a warning. It is a boot abort. The pattern, validate secrets at startup and refuse to run with known-bad defaults, is security-correct but rare in open-source tooling. Most projects document "please change this in production" in a README and trust the operator. OpenShip encodes the enforcement in the runtime. Combined with OPENSHIP_REQUIRE_REDIS=true disabling the silent in-memory queue fallback, this signals a design philosophy that prefers loud failures over silent degradation. That philosophy will frustrate developers who want to get something running quickly and delight operators who have been burned by silent misconfiguration in production.

TL;DR For Engineers

  • OpenShip (oblien/openship, AGPL-3.0, 2.9k stars) is a self-hosted PaaS where builds run on your local machine and finished OCI images stream to the target server over SSH. The server runs no platform software.

  • Stack: TypeScript monorepo (Turborepo + Bun), API on port 4000, dashboard on 3001, Postgres 16 + Redis 7 as internal services, OpenResty as the reverse proxy with embedded Lua for zero-downtime swaps and automatic Let's Encrypt.

  • CLOUD_MODE=false (default) is self-hosted with no billing. CLOUD_MODE=true activates GitHub App auth, Oblien billing metering, and multi-tenant workspace isolation. Same codebase, same compose file.

  • Three interfaces: CLI (openship deploy, openship rollback, openship logs --follow), web dashboard, native desktop app. Plus REST API and an MCP server for AI agent integration.

  • The server overhead is one OpenResty container at ~18MB RAM. Everything else on the server is your application.

Explain It Like I'm New

Every developer eventually hits the same wall: you have an app, and you need it to run somewhere reliably, with automatic SSL certificates, custom domain names, and the ability to push an update without taking the site down. The standard options are either expensive managed platforms (Vercel, Heroku, Render) that charge for every feature and lock you into their infrastructure, or you configure a server yourself from scratch, which is a full-time job.

Self-hosted tools like Coolify try to solve this by running a deployment platform on your server. The problem is that the platform itself consumes resources. Your $20/month VPS is now running the deployment dashboard, the build system, the CI runner, a database for the platform's own state, and your actual application, all competing for the same CPU and memory.

OpenShip makes a different bet. The platform runs on your laptop or in a lightweight cloud service, not on your production server. Your code is built into a container image on your machine, and that finished image is sent to the server over a secure SSH connection. The server's only job is to run the container and serve traffic.

The result is that a $6 virtual server spends nearly all its resources on your application, not on managing itself. The deployment platform, the build pipeline, the admin dashboard, all of that stays off the server entirely. This makes self-hosting meaningfully cheaper and more predictable, especially as you add more applications.

See It In Action

  • Openship Quick Start Demo Source: openship.io | https://openship.io The homepage walkthrough shows the five-step flow from git push to live container, including the local build, SSH stream, OpenResty routing, and Let's Encrypt cert issuance. Concise and accurate to the actual deployment path.

  • GitHub Repo and README (oblien/openship) Source: GitHub | https://github.com/oblien/openship The README contains the architecture diagram showing the clean separation between the local control plane and the server compute layer, plus the full feature comparison table against Coolify, CapRover, and Dokku.

  • MCP Server Integration Reference Source: openship.io/docs | https://openship.io/docs/api Documents the MCP endpoint that allows AI agents (Claude, Cursor, any MCP-compatible client) to issue deploys, roll back, stream logs, and manage domains. Directly relevant to agentic infrastructure workflows.

Community Conversation

  • @openshipio on X https://x.com/openshipio The official account announces releases and shares deployment benchmarks. The thread announcing the MCP server integration generated the most technical discussion around using Openship as the deployment target for agentic AI workflows.

  • oblien/openship GitHub Issues https://github.com/oblien/openship/issues Active discussion on multi-node cluster support (on the roadmap), load balancing UI, and private networking. The issue tracker is honest about the production-ready core and the capabilities still in development, which is a useful signal for teams evaluating adoption.

  • Discord (discord.gg/openship) https://discord.gg/openship Community channel where self-hosters share VPS provider comparisons (Hetzner vs DigitalOcean vs OVH), image size optimization for faster SSH streaming, and custom OpenResty Lua configurations. Practical signal on real adoption patterns.

Local Builds Are Not a Quirk, They Are the Architecture

OpenShip is not a Coolify clone with a different UI. The decision to keep the control plane off the server is a genuine systems design choice that changes the economics, the failure modes, and the security surface area of self-hosted deployment. Whether that tradeoff fits your team depends on your build times, your network bandwidth, and whether you want your CI to live on the same machine as your production traffic.

The MCP integration is the piece most coverage is ignoring. A deployment platform with a standard AI agent interface is not a convenience feature. It is a bet on where infrastructure tooling is going: agents that can autonomously deploy, observe, and recover production systems. OpenShip is positioning that bet now, while the space is still early.

References

OpenShip is a self-hosted deployment platform that moves the entire build and control plane off your production server, streaming finished OCI container images to the target over SSH. The single-flag mode switch (CLOUD_MODE) runs the same codebase in self-hosted or full SaaS mode. The MCP server integration, which lets AI agents issue deploys and rollbacks directly, positions OpenShip as the deployment layer for agentic infrastructure workflows, the most differentiated bet in an otherwise crowded self-hosted PaaS space.

Sponsored Ad

If you enjoy practical AI insights, check out SnackOnAI and support the newsletter by subscribing, sharing, and exploring our sponsored ad — it helps us keep building and delivering value 🚀

Speak naturally. Send without fixing.

Wispr Flow turns your voice into clean, professional text you can send the moment you stop talking. Not rough transcription you have to clean up. Actual polished text — ready for email, Slack, or any app.

Speak the way you think. Go on tangents. Change your mind mid-sentence. Flow strips the filler, fixes the grammar, and gives you text that reads like you spent five minutes writing it.

89% of messages sent with zero edits. Millions of professionals use Flow daily, including teams at OpenAI, Vercel, and Clay. Works on Mac, Windows, and iPhone.

Recommended for you