The Problem

Teams that want a private CI/CD pipeline often spin up multiple bespoke tools (GitHub Actions, custom Docker builds, ad‑hoc scripts) and then have to maintain them separately. Openship aims to replace that stack with a single, self‑hosted control plane that can watch a repo, build the artefact, expose it via TLS, and route traffic – all without a public SaaS dependency.

What This Does

The repo is a monorepo of four independent projects:

ProjectMain purposeNotable entry points
apps/apiCore control‑plane service (REST/GraphQL)apps/api/src/app.ts, apps/api/src/index.ts
apps/dashboardWeb UI built with React + Next.jsapps/dashboard/src/pages/_app.tsx
apps/cliCommand‑line client that drives the control planeapps/cli/src/index.ts
apps/desktopElectron wrapper for the UI (desktop app)apps/desktop/main.ts

Common techniques across the projects are TypeScript, Docker containerisation, and GitHub Actions for CI. Shared utility code lives in packages/ (e.g., packages/tsconfig and common type definitions) and is imported by the apps via workspace references.

How It Is Wired

Execution begins in apps/api/src/app.ts – the file creates an Express (or Fastify) server, registers middleware, and finally calls app.listen. The server imports:

  • apps/api/src/config/index.ts – reads environment variables (via dotenv) from .env.example or the real .env files.
  • apps/api/src/lib/cache-store/index.ts – selects a cache implementation (memory.ts or redis.ts) based on CACHE_TYPE. This module is the only point that touches external state (Redis host/port).
  • apps/api/src/lib/cloud/* – client code that talks to remote SSH hosts or cloud VMs; the functions cloudAuthProxy, cloudPreflight, and cloudRouteService are the sole network‑outbound calls from the API.

All request handlers are defined in apps/api/src/lib/* (e.g., auth.ts, project-router.ts). Each handler ultimately calls a service in apps/api/src/lib/cloud or updates the local SQLite DB (SQL files are under apps/api/migrations). The wide blast radius lies in app.ts (CORS wildcard) and the cache store selector – a change there can affect every incoming request.

The UI (apps/dashboard) is a standard Next.js app that talks to the API via the /api endpoints defined above. The CLI (apps/cli) imports the same configuration module (apps/api/src/config) and invokes the same service functions, so business logic is not duplicated.

apps/desktop packages the dashboard with Electron; its entry point apps/desktop/main.ts spawns a Chromium window that loads the locally built dashboard assets.

packages/ contain reusable TypeScript configs and type definitions. No circular imports were detected, but apps/api/src/lib/cache-store/index.ts acts as a hub for the cache implementations, meaning any modification to its export shape ripples through all request handlers.

How To Use It

# Clone the repo
git clone https://github.com/moses-y/openship
cd openship

# Install dependencies (Bun is used; .bun-version is present)
bun install

# Build the API Docker image (Dockerfile in apps/api)
docker build -t openship-api -f apps/api/Dockerfile .

# Run the API container, mounting a fresh env file
docker run -p 8000:8000 \
  -v $(pwd)/apps/api/.env.example:/app/.env \
  openship-api

The API expects variables defined in apps/api/.env.example (e.g., DATABASE_URL, CACHE_TYPE). For local development replace the mounted file with a copy of .env.example and fill in real values.

To start the web UI locally:

cd apps/dashboard
bun dev   # runs Next.js dev server on http://localhost:3000

The desktop client can be launched after bun install in apps/desktop:

bun run electron .

Real‑World Use

A small SaaS team can deploy Openship on a single VPS. The CI workflow (.github/workflows/ci.yml) builds the Docker image, pushes it to a private registry, and the API then pulls the image to run user applications on demand. The dashboard provides a “one‑click deploy” button that triggers the same API endpoint used by the CLI.

Code Health & Issues

Static analysis reported the following findings (severity‑ordered):

  • Critical – Rotate credentials in apps/api/.env.migtest.
  • High – Pin GitHub Actions to commit SHAs (.github/workflows/*).
  • High – Remove committed .env files and rotate their contents (apps/api/.env.migtest, apps/email/client/.env.development).
  • High – Stop discarding exit codes in CI step (line 56 of ci.yml).
  • High – Replace wildcard CORS (*) with an explicit allow list (apps/api/src/app.ts).
  • Medium – Declare least‑privilege GITHUB_TOKEN permissions (ci.yml).
  • Medium – Enable Dependabot or Renovate.
  • Medium – Pin Docker base image by digest (apps/api/Dockerfile).
  • Medium – Add a dependency‑vulnerability scan to CI.
  • Medium – Move large binaries (e.g., apps/email/client/public/onboarding/step2.gif) to Git LFS.

Additional hygiene notes: tests exist (656 files) and CI runs, but the repo lacks a lockfile and contains committed secrets.

The Bottom Line

Openship provides a coherent, Docker‑first self‑hosted CI/CD platform with a clear separation between API, UI, CLI, and desktop client. The codebase is sizeable and functional, but the presence of committed secrets, unpinned dependencies, and a permissive CORS policy represent serious production risks that must be remedied before public deployment. Suitable for teams comfortable managing their own infra and willing to address the listed security and reproducibility gaps.