Skip to main content
Shipyard runs entirely on your local machine during development. You can build and deploy projects, access them via subdomain routing without editing /etc/hosts, stream real-time build logs over WebSockets, and run the full test suite — all without a remote server.

Local development setup

For the complete environment setup including PostgreSQL, GitHub OAuth app configuration, and environment variables, see the quickstart guide. Once you have the server running, the steps below cover testing-specific workflows. Start the server with hot reload enabled:
npm run dev
This uses tsx watch to automatically restart the server whenever a source file changes — no manual restarts needed during development.

Accessing deployed projects locally

Shipyard uses subdomain-based routing to serve deployed projects. In production this works naturally with a real domain, but locally you need subdomains that resolve to 127.0.0.1. lvh.me is a public domain that resolves all subdomains to 127.0.0.1. No /etc/hosts edits or DNS configuration required — it just works. Set the following in your .env:
BASE_DOMAIN=lvh.me:8080
After deploying a project, access it at:
http://<project-name>.lvh.me:8080
For example, a project named test-repo is available at:
http://test-repo.lvh.me:8080

Running the test suite

npm test
This runs 8 tests via Mocha covering authentication, project creation, repository browsing, and webhook handling. All tests must pass before submitting changes.

WebSocket testing

Shipyard streams build output and status changes to connected clients over Socket.io. The server uses JWT authentication — pass your token in the auth object when connecting. Once connected, the server joins you to a room named after your userId, and all events for your projects are emitted to that room. The available server-to-client events are:
EventDescription
build_logsReal-time stdout from the Docker build step
build_errorsStderr from the Docker build step
run_logsStdout from the container run step (install + build command)
run_errorStderr from the container run step
buildStatusUpdateBuild status changes: running, passed, or failed
deploymentUpdateDeployment completion with the live URL
Connect and listen for events:
import { io } from "socket.io-client";

const socket = io("http://localhost:8080", {
  auth: { token: "<your-jwt>" }
});

socket.on("build_logs", (data) => {
  console.log(`[${data.lineNumber}] ${data.log}`);
});

socket.on("buildStatusUpdate", (data) => {
  console.log(`Build ${data.buildId}: ${data.status}`);
});

socket.on("deploymentUpdate", (data) => {
  console.log(`Deployed at: ${data.url}`);
});
Each event payload includes projectId and buildId so you can route updates to the correct UI component when multiple projects are building simultaneously.

Health check

curl http://localhost:8080/health
Returns { "message": "OK" } when the server is running and accepting requests. Use this to verify the server started successfully or to check liveness in automated scripts.
Use npm run dev whenever you’re actively working on the server. The tsx watch process restarts in under a second on file changes, which makes iterating on controllers, services, and routes much faster than stopping and restarting manually.

Build docs developers (and LLMs) love