Engineering

Hosted Deployment#

Co-Vibe can be hosted as a single Next.js container.

The hosted app is the customer control plane: WorkOS login, tenants, workspaces, dashboards, MCP API, verified telemetry storage, and reports. Developers still install the local companion in each tracked repo and point COVIBE_BASE_URL at this hosted URL.

The checked-in Docker path is still useful for local and single-host rehearsal, and it now uses PostgreSQL rather than a file-backed app database. The GCP production target is Cloud Run plus Cloud SQL for PostgreSQL. PostgreSQL schema initialization and a runtime bridge now exist; run both PostgreSQL smokes before the production doctor is allowed to pass. See gcp-production.md.

Container Path#

The repo ships a provider-neutral Dockerfile.

It uses Next.js standalone output:

  • next.config.ts sets output: "standalone".
  • Dockerfile runs npm ci and npm run build.
  • The dependency stage uses a BuildKit npm cache mount plus npm fetch retries so customer image builds tolerate transient registry resets.
  • The runtime image runs node server.js from .next/standalone.
  • public and .next/static are copied into the runtime image.
  • The ONNX native runtime bin is copied explicitly because Next standalone does not trace the adjacent shared library needed by @huggingface/transformers.
  • /app/data is a volume for profile-photo uploads.
  • .github/workflows/customer-readiness.yml runs readiness and a container runtime smoke on pull requests, pushes to main, and manual dispatch. When WorkOS secrets are configured, CI also runs the full npm run readiness:customer handoff gate; when COVIBE_HOSTED_URL is also configured, or a manual dispatch provides hosted_url, CI runs npm run readiness:handoff -- "$COVIBE_HOSTED_URL" against the permanent domain. Otherwise those jobs report skips.

Required runtime env:

  • COVIBE_AUTH_MODE=workos
  • WORKOS_CLIENT_ID
  • WORKOS_API_KEY
  • WORKOS_COOKIE_PASSWORD
  • NEXT_PUBLIC_WORKOS_REDIRECT_URI=https://<host>/callback
  • COVIBE_DATABASE_BACKEND=postgres
  • DATABASE_URL=postgres://... (NON-superuser, non-owner app role — subject to RLS)
  • COVIBE_POSTGRES_ADMIN_URL=postgres://... (owner/superuser, init + provisioning only; required for self-contained auto-init with privilege separation)
  • COVIBE_UPLOAD_DIR=/app/data/uploads

Optional runtime env:

  • COVIBE_HOSTED_URL
  • WORKOS_COOKIE_MAX_AGE
  • COVIBE_REMEMBER_ME_DAYS
  • COVIBE_COMPANION_INSTALL_COMMAND
  • COVIBE_PAXEL_ENABLED / COVIBE_PAXEL_STALE_MS (see paxel-integration.md)
  • ANTHROPIC_API_KEY
  • COVIBE_SUMMARY_MODEL

Our Hosted Docker Profile#

For a VPS or managed Docker host, use the checked-in Compose profile:

bash
export COVIBE_HOSTED_URL=https://co-vibe.example.com
export NEXT_PUBLIC_WORKOS_REDIRECT_URI=https://co-vibe.example.com/callback
export WORKOS_CLIENT_ID=...
export WORKOS_API_KEY=...
export WORKOS_COOKIE_PASSWORD=...
docker compose up -d --build

docker-compose.yml binds the app to 127.0.0.1:3000 by default for a reverse proxy such as Caddy, nginx, or a load balancer that terminates HTTPS. Set COVIBE_BIND=0.0.0.0 only when another network layer restricts access. Runtime state lives in the named covibe-data volume mounted at /app/data, which stores profile uploads across container restarts. App state lives in PostgreSQL. The Postgres service bootstraps covibe_owner as the superuser/owner used via COVIBE_POSTGRES_ADMIN_URL; on first boot the app connects as the owner to create the schema and provision the non-superuser covibe_app role named in DATABASE_URL, then runs all subsequent queries as that least-privilege role so row-level security is enforced. COVIBE_HOSTED_URL is forwarded into the container when present so in-container doctor/readiness checks use the same origin as the operator shell. The Compose profile includes a PostgreSQL service for rehearsal; production Cloud Run should use Cloud SQL instead.

For a single-host HTTPS deployment, point DNS at the machine, allow ports 80 and 443 through the host firewall, make sure the WorkOS dashboard allowlists the same deployed callback, then run the proxy profile:

bash
export COVIBE_HOSTED_URL=https://co-vibe.example.com
export COVIBE_HOSTNAME=co-vibe.example.com
export NEXT_PUBLIC_WORKOS_REDIRECT_URI=https://co-vibe.example.com/callback
export WORKOS_CLIENT_ID=...
export WORKOS_API_KEY=...
export WORKOS_COOKIE_PASSWORD=...
docker compose --profile proxy up -d --build

The proxy profile starts Caddy from deploy/caddy/Caddyfile, terminates HTTPS for COVIBE_HOSTNAME, and forwards traffic to the private covibe:3000 container. Caddy certificate and account state live in caddy-data and caddy-config volumes so renewals survive restarts.

After the proxy/domain is live, run:

bash
npm run readiness:handoff -- https://co-vibe.example.com

Release Gate#

Before every pull request or internal release:

bash
npm run readiness

CI runs the same command on a clean Ubuntu runner and separately runs:

  • npm run smoke:package inside readiness to prove the companion package installs and configures a clean external repo.
  • npm run smoke:container in the container job to build the image, boot it, and verify production auth fails closed when WorkOS env is absent.
  • npm run readiness:customer in a conditional customer-handoff job when WorkOS secrets are configured.

npm run build first runs npm run build:companion, which stages a minimal companion manifest and packs public/downloads/co-vibe.tgz. The companion tarball depends only on the local runtime packages it needs, not on the hosted app's Next/WorkOS/database/embedding dependencies. The hosted container serves that file from /downloads/co-vibe.tgz, and Agent Setup uses the current host origin in the copied install command. Set COVIBE_COMPANION_INSTALL_COMMAND only when overriding the hosted tarball with a public npm package or internal registry command. Unsafe overrides with shell chaining, comments, line continuations, token-looking values, or --token fall back to the hosted tarball. If the command is public npm, verify publication first: npm run security also runs npm run check:package, which allowlists the tarball contents, rejects unexpected dependencies, and rejects env files, local state, app routes, tests, reports, or broad server code before the package can be shipped.

bash
npm view co-vibe version
bash
npm run smoke:container

The container smoke first runs a short Docker daemon probe. If Docker Desktop or the host daemon is not responding, the script fails before starting the image build and tells the operator to restart Docker.

With WorkOS env available, also run the WorkOS-mode image smoke:

bash
npm run smoke:container:workos

It builds the same image, passes WorkOS secrets by environment name so raw values are not echoed in command args, overrides only the temporary localhost callback, and verifies health, sign-in, protected API rejection, hidden local-dev routes, and invalid MCP-token rejection inside the container.

With production env available:

bash
npm run db:postgres:init
npm run smoke:postgres
npm run smoke:postgres:runtime
npm run doctor:production

Use npm run doctor:production -- --help to inspect the required WorkOS and hosted-release env without loading credentials first. The production doctor loads the same .env* files as Next, rejects local-dev auth, checks cookie strength, validates the callback path, and when COVIBE_HOSTED_URL is set for a non-localhost deployment requires the WorkOS redirect origin to match the hosted origin. For real hosted URLs it also requires either the generated hosted tarball or COVIBE_COMPANION_INSTALL_COMMAND, so the Agent Setup page cannot silently advertise an unpublished package.

Before a real HTTPS deploy is available, operators can rehearse the hosted canary against a locally running container:

bash
npm run readiness:container:hosted

This command builds the image, boots a temporary WorkOS-mode container with a matching localhost callback, then runs the same production doctor and hosted canary checks used after deployment. It passes the explicit canary-only --allow-local-rehearsal flag internally; the real handoff command rejects localhost and non-HTTPS URLs.

To rehearse the exact public HTTPS handoff before a permanent domain exists, install and authenticate the ngrok CLI, then run:

bash
npm run readiness:hosted:https-rehearsal

That command builds the hosted image, opens a temporary public HTTPS tunnel to a WorkOS-mode container, sets the matching callback URL, and runs the real npm run readiness:hosted -- https://... command without the localhost rehearsal flag. It proves the HTTPS canary contract, but it is not a durable customer deployment or stable customer domain.

Before customer handoff, run the full customer rehearsal with Docker and WorkOS env available, then run the strict handoff wrapper against the deployed permanent HTTPS origin:

bash
npm run readiness:customer
npm run readiness:handoff -- https://<host>

The handoff wrapper rejects missing, localhost, HTTP, credential-bearing, callback/path/query/hash URLs, private-network hosts, and temporary tunnel domains before downstream checks. With a valid permanent origin it first runs a fast /api/health, /api/mcp, and /downloads/co-vibe.tgz preflight so DNS, TLS, WorkOS mode, MCP tool-count/surface, missing companion-package, broken sign-in, public protected-API issues, unauthenticated token creation, or exposed local-only routes, malformed MCP envelopes, or missing/invalid MCP-token rejection, token-shaped response leaks, or a broken/non-Co-Vibe companion tarball fail before the long local gate. It then runs doctor:production against the same hosted origin before customer readiness and remote hosted readiness. Customer setup and operator details live in hosting-handoff.md.

View as .md