Skip to main content
Shipyard is configured entirely through environment variables. Create a .env file in the project root before starting the server — the application reads it on startup via dotenv. All variables listed below are required; the server will behave incorrectly or fail to start if any are missing.
Never commit your .env file to version control. Add it to your .gitignore before your first commit. It contains secrets that grant access to your database, GitHub account, and encrypted project data.

Database

DATABASE_URL
string
required
PostgreSQL connection string. Shipyard uses this to connect to the database through pg and Drizzle ORM.
DATABASE_URL=postgresql://user:password@localhost:5432/pipeline

GitHub OAuth

Shipyard uses GitHub OAuth for user authentication. You obtain these values when you create an OAuth App in your GitHub account under Settings → Developer Settings → OAuth Apps.
CLIENT_ID
string
required
The client ID from your GitHub OAuth App. Shipyard includes this in the authorization URL it redirects users to when they sign in.
CLIENT_ID=Iv1.a1b2c3d4e5f6g7h8
CLIENT_SECRET
string
required
The client secret from your GitHub OAuth App. Shipyard sends this to GitHub during the token exchange step of the OAuth flow. Keep this value confidential — treat it like a password.
CLIENT_SECRET=your_github_oauth_client_secret

Authentication and encryption

SECRET
string
required
Secret used to sign JWT session tokens. Shipyard issues a JWT after a successful GitHub OAuth login and verifies it on every authenticated API request. Use a long, random string — this value should never be guessable.
SECRET=your_jwt_signing_secret
ENCRYPTION_KEY
string
required
A 32-byte key encoded as a 64-character hex string, used for AES-256-GCM encryption of project secrets. Shipyard encrypts environment variables before storing them in the database and decrypts them only at build time — they are never returned in API responses.Generate a secure key with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
ENCRYPTION_KEY=a1b2c3d4e5f6...  # 64 hex characters
WEBHOOK_SECRET
string
required
Secret used to verify the HMAC-SHA256 signature on incoming GitHub webhook payloads. When you connect a repository, Shipyard registers this secret with GitHub. On each push event, Shipyard recomputes the signature using this value and compares it with the X-Hub-Signature-256 header using a timing-safe comparison to prevent brute-force attacks.
WEBHOOK_SECRET=your_webhook_secret

Routing and URLs

BASE_DOMAIN
string
required
The base domain used for subdomain routing of deployed projects. When a project named my-app is deployed, it is served at my-app.<BASE_DOMAIN>.For local development, use lvh.me:8080lvh.me is a public DNS entry that resolves all subdomains to 127.0.0.1, so my-app.lvh.me:8080 reaches your local Shipyard server without any extra configuration.
BASE_DOMAIN=lvh.me:8080
On a VPS, set this to your actual domain (e.g., useshipyard.xyz).
FRONTEND_URL
string
required
The URL of your frontend application. Shipyard uses this value in two places:
  • CORS: only requests from this origin are permitted
  • OAuth redirect: after GitHub OAuth completes, Shipyard redirects the user back to this URL with the JWT token appended
FRONTEND_URL=http://localhost:3000
WEBHOOK_CALLBACK
string
required
The public URL where GitHub sends webhook events. When you connect a repository, Shipyard registers this URL as the webhook endpoint via the GitHub API. It must be publicly reachable — GitHub cannot send events to localhost.
WEBHOOK_CALLBACK=https://your-tunnel-url.ngrok.io
During local development, use a tunneling tool to expose port 8080 and use the tunnel URL here. Update this value and restart the server whenever your tunnel URL changes.
# ngrok
ngrok http 8080

# outray
outray 8080

Full example

A complete .env for local development:
DATABASE_URL=postgresql://shipyard:secret@localhost:5432/pipeline
CLIENT_ID=Iv1.a1b2c3d4e5f6g7h8
CLIENT_SECRET=your_github_oauth_client_secret
SECRET=a_long_random_jwt_signing_secret
ENCRYPTION_KEY=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2
WEBHOOK_SECRET=another_random_secret
BASE_DOMAIN=lvh.me:8080
WEBHOOK_CALLBACK=https://abc123.ngrok.io
FRONTEND_URL=http://localhost:3000

Build docs developers (and LLMs) love