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:
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:
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
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:
| Event | Description |
|---|
build_logs | Real-time stdout from the Docker build step |
build_errors | Stderr from the Docker build step |
run_logs | Stdout from the container run step (install + build command) |
run_error | Stderr from the container run step |
buildStatusUpdate | Build status changes: running, passed, or failed |
deploymentUpdate | Deployment 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.