Skip to main content
When a build passes and the project has no environment variables, Shipyard automatically promotes the build output to a live deployment. The deployment engine (src/services/deploymentEngine.ts) copies the files, records the deployment in the database, and updates the project’s production URL — all without any manual intervention. The subdomain middleware in src/index.ts then takes care of routing incoming requests to the right directory.

How deployment works

Copying build output

The deployment engine resolves the output path from the temporary build directory:
  • If an outputDir was determined (by framework detection or the project’s configured value), files are copied from temp/<folder>/<outputDir>/.
  • If no outputDir applies (for example, projects without a build command), the root of the build directory is used.
The contents are copied to a permanent location on disk:
deployments/<project-name>/
Any previous deployment directory at that path is removed before the copy so the deployment is always a clean replacement.

Production URL

The production URL is constructed from the project name and the BASE_DOMAIN environment variable:
https://<project-name>.<BASE_DOMAIN>
This URL is written to projectTable.productionUrl in the database and emitted to the client in a deploymentUpdate Socket.io event.

Deployment record

A row is inserted into the deployment table with status: "live" and a reference to the build that produced it. For rollbacks, this insert is skipped because the existing deployment record is reused — only the files on disk and the productionUrl are updated.

Deployment status values

StatusMeaning
liveThe deployment is active and being served
rolled_backThe deployment was superseded by a rollback to an earlier build

Database schema

deploymentTable = {
  id:        bigint   // auto-generated primary key
  status:    enum     // "live" | "rolled_back"
  buildId:   bigint   // foreign key → build.id (cascade delete)
  createdAt: timestamp
}

Subdomain routing middleware

Every HTTP request to the server first passes through the subdomain middleware before reaching any API route. The middleware:
  1. Reads the Host header from the request.
  2. Splits on . and takes the first segment as the subdomain.
  3. Checks whether deployments/<subdomain>/ exists on disk.
  4. If the directory exists, serves it with express.static. Files that exist are returned directly (CSS, JS, images, etc.).
  5. If no static file matches the request path, falls back to deployments/<subdomain>/index.html — this enables client-side routing in single-page applications.
  6. If the directory does not exist, calls next() and the request continues to the API routes.
The www subdomain is excluded from this logic so it never accidentally matches a deployment directory.
Only projects without secrets are automatically deployed as static sites. If a project has environment variables, the build pipeline sets a hasEnvFile flag and skips the deployProject call. Shipping secrets inside a statically served build would expose them publicly.
For local development and testing, use lvh.me as your BASE_DOMAIN. The domain lvh.me and all of its subdomains resolve to 127.0.0.1, so http://<project-name>.lvh.me:8080 routes directly to your local server without any hosts-file edits.
BASE_DOMAIN=lvh.me:8080
A project named my-app would then be accessible at http://my-app.lvh.me:8080.

Build docs developers (and LLMs) love