← All docs

Deploy with the API (CLI, CI & AI agents)

Deploy from the UI, from the CLI with an API token, or let an AI coding agent do it — with logs, deployment codes, and a full troubleshooting reference.

Two ways to deploy

  • Manual (UI): open your project, pick an environment, and click Deploy — see Getting started.
  • Headless (API token): authenticate with a personal access token and call the API from a script, CI, or an AI coding agent.
  • Either way, Shipyard builds from your connected Bitbucket repo — so push your code to the branch first, then trigger a deploy of that commit. With auto-deploy enabled on an environment, a push deploys automatically and you can skip the API call entirely.

1. Create an API token

  • In the console go to API tokens → New token, give it a name, and copy the value (shown once). It starts with shpat_.
  • A token acts as you, scoped to your workspace; it has no platform-admin access. Send it as a header: Authorization: Bearer shpat_…
  • Set your base URL + token as shell variables for the steps below:
  • export SHIPYARD_API=https://api.example.com
  • export SHIPYARD_TOKEN=shpat_xxxxxxxx

2. Find your workspace, project and environment

  • Your workspace (tenant) id: curl -s -H "Authorization: Bearer $SHIPYARD_TOKEN" $SHIPYARD_API/auth/me (use memberships[0].tenantId)
  • Your projects: curl -s -H "Authorization: Bearer $SHIPYARD_TOKEN" $SHIPYARD_API/tenants/<tenantId>/projects
  • A project’s environments: curl -s -H "Authorization: Bearer $SHIPYARD_TOKEN" $SHIPYARD_API/tenants/<tenantId>/projects/<projectId> (use environments[].id)

3. Trigger a deploy

  • POST to the environment’s deployments endpoint (optionally pass a branch + note):
  • curl -s -X POST -H "Authorization: Bearer $SHIPYARD_TOKEN" -H "Content-Type: application/json" -d '{"note":"deploy from CLI"}' $SHIPYARD_API/tenants/<tenantId>/projects/<projectId>/environments/<environmentId>/deployments
  • The response includes the deployment id. Poll its status: curl -s -H "Authorization: Bearer $SHIPYARD_TOKEN" $SHIPYARD_API/tenants/<tenantId>/deployments/<deploymentId>
  • Roll back or retry from the project’s Deployments tab, or via the rollback/retry endpoints.

Let an AI coding agent deploy for you

  • Create a token, then tell your AI coding agent: “Deploy this app to Shipyard — read https://example.com/docs/deploy and use SHIPYARD_API + SHIPYARD_TOKEN from my environment.”
  • The agent can push your code to Bitbucket, then run the calls above (resolve tenant → project → environment → POST a deployment) and report the live URL.
  • Keep the token in an environment variable or your secrets manager — never commit it. Revoke it anytime from the API tokens page.

Attach a managed database

  • Create a PostgreSQL or MongoDB under Databases. On creation (or later via “Use in app”) choose to inject its connection URI as a secret — into one project or the whole workspace.
  • Your app then reads DATABASE_URL (Postgres) or MONGODB_URI (Mongo) from the environment at build + runtime. Secrets stay scoped to your workspace only.

Identify a deployment by its code

  • Every deployment has a platform-wide unique reference like DEP-1042 — unique across the entire instance, not just your project. It is shown in the console (Deployments list + detail header) and returned by the API as deploymentRef (with the raw number as globalNumber).
  • A smaller per-project number (#7) still appears next to it. Quote the DEP- code when asking your AI agent or support to investigate a specific deploy — it identifies one deployment unambiguously at any scale.

Where to see logs (and copy errors)

  • Build logs — every deploy, live + historical, on the deployment detail page. API: GET $SHIPYARD_API/tenants/<tenantId>/deployments/<deploymentId>/logs (and an SSE stream at /stream).
  • Node runtime logs — a Node app’s container stdout/stderr, on the deployment detail page. API: GET $SHIPYARD_API/tenants/<tenantId>/releases/<releaseId>/runtime-logs (and /stream).
  • Static/React access log — static apps run NO process, so there is no stdout/stderr. Instead an Access log on the deployment detail page records requests the origin handled that failed (4xx/5xx) or were not a page load (e.g. a form POST). API: GET $SHIPYARD_API/tenants/<tenantId>/projects/<projectId>/environments/<environmentId>/access-logs (and /stream).
  • Every failure also sets the deployment’s error field (returned by GET $SHIPYARD_API/tenants/<tenantId>/deployments/<deploymentId>) and is folded verbatim into the build log — so any error can be copied straight out and pasted to an AI agent. Secret values are redacted automatically.

Static & React apps have no backend

  • A Static or React project is built once and served as files (a CDN-style origin). There is NO server running for it. So a form submit or a fetch to /api/… returns 404 Not Found — the response body reads “Cannot POST /_sites/…”. This is the most common “it worked on my machine” surprise.
  • Why it works locally: your dev server (Vite/CRA proxy) or a local API process answers that request during development — but that process is not part of a static build, so it does not exist in staging/production.
  • Fix (best, if your repo already has a server.js): set Runtime type = “Node server” (Project → Settings → Runtime configuration, or the “runs its own server” option at create time). Shipyard then builds the frontend and runs your Express/SSR server, which serves the built site AND /api on one origin — your relative /api/submit just works, no CORS. Your server must listen on process.env.PORT. See “React/Static apps that run their own server”.
  • Fix (if the API is a separate codebase): deploy your API as its own Node project and point the form’s submit URL at that app’s domain via a build-time variable (VITE_API_URL / REACT_APP_API_URL) under Secrets; enable CORS on the backend for your site’s domain. Or post to an external API / form-handling service. A purely static app cannot process its own submissions.
  • Diagnose it: open the failing deploy → Access log; the POST will appear as a 404. (A GET to an unknown route returns 200 because SPA routing falls back to index.html — only non-GET / API calls 404.)

Troubleshooting — build errors

  • “npm ci can only install with an existing package-lock.json” — the install command is npm ci but there is no lockfile at the build root directory. Fix: commit package-lock.json, or set the install command to npm install. (Shipyard auto-falls back to npm install when no lockfile is present, so also check your Root directory is correct.)
  • “Missing script: build” / “npm run build” fails immediately — the build command runs a script your package.json does not define. Fix: add the build script, or clear the build command. (Shipyard skips npm run <script> automatically when the script is absent.) Static sites with pre-committed files need no build command at all.
  • Build succeeds but the site is blank or 404s on every asset — the Output directory is wrong. Set it to the folder that contains index.html: Vite → dist, Create-React-App → build, plain static → public.

Troubleshooting — runtime & database errors (Node & Python)

  • “self-signed certificate” / DEPTH_ZERO_SELF_SIGNED_CERT — your app forces TLS verification on the database connection. An attached managed DB is reached over the private network and needs no TLS. Fix: remove the ssl option from your client/pool, or set ssl: { rejectUnauthorized: false }.
  • “App failed its health check” / “fetch failed” / ECONNREFUSED — the server did not listen on the assigned port, or the health-check path did not return 200 in time. Fix: bind to the port Shipyard injects as PORT (Node: process.env.PORT; Python: gunicorn app:app --bind 0.0.0.0:$PORT or uvicorn main:app --host 0.0.0.0 --port $PORT), and bind 0.0.0.0. Traffic only switches to a release after it passes the check.
  • Python: “ModuleNotFoundError” / gunicorn not found — your deps did not install into the venv. Shipyard installs into .venv inside the app (python -m venv .venv && .venv/bin/pip install -r requirements.txt) and puts .venv/bin on PATH at runtime; make sure requirements.txt is at your root directory and lists gunicorn/uvicorn.
  • “Missing DB_URI” / “DATABASE_URL is undefined” — your app reads a different env-var name than the secret key. Fix: attach the database under the exact key your code reads, or rename the secret in the project’s Secrets. Postgres defaults to DATABASE_URL, Mongo to MONGODB_URI.

Troubleshooting — quotas & API auth

  • 403 “deployments … limit” — you reached the workspace’s daily deployment quota. Fix: wait for the window to reset, or ask a platform operator to raise it.
  • 401 / 403 from the API — the token is missing, invalid, expired or revoked, or you called a platform-admin route. Tokens act as you and are workspace-scoped only. Fix: re-issue a token from API tokens, send it as Authorization: Bearer shpat_…, and only call tenant routes (/tenants/<tenantId>/…).
  • A URL is not available yet — a default URL is withheld until a deploy SUCCEEDS. If the env shows “becomes available after the first successful deploy”, the latest deploy has not gone live; check its build log/error.

Automated deploys: the loop an AI agent should run

  • Push the commit to the branch → POST a deployment → poll GET $SHIPYARD_API/tenants/<tenantId>/deployments/<deploymentId> until status is a terminal value (succeeded / failed / cancelled).
  • On failed: read the error field AND the build log (and the runtime or access log for the app type). Map the message against the troubleshooting sections above, apply the fix, and redeploy. Reference the deploy by its DEP- code.
  • On succeeded: fetch the environment’s runtime (GET …/projects/<projectId>/environments/<environmentId>/runtime) to report the live URL.