← All docs

Supported app types

Static sites, React SPAs, Node.js services and Python web apps — with the exact settings for each.

Static site

  • For plain HTML/CSS/JS or any pre-built static output. No server runs; files are served directly over HTTPS.
  • Build command: leave empty if the files are already committed, or your generator command (e.g. npm run build).
  • Output directory: the folder containing index.html (default: public). Root directory: / (or a subfolder).
  • Example: a repo with public/index.html → type Static, output “public”, no build command.

React app (SPA)

  • For Vite / Create-React-App / any single-page app that builds to static assets. Served with SPA fallback (unknown routes return index.html).
  • Install command: npm ci. Build command: npm run build. Output directory: dist (Vite) or build (CRA).
  • Build-time variables (e.g. VITE_*/REACT_APP_*) are set under the project’s Secrets/variables before building.
  • No server runs in this mode — so a form POST or a fetch to /api/… returns 404. If your app needs a backend, see “React/Static apps that run their own server” below.

React/Static apps that run their own server

  • If your React or Static project ships its OWN Node server (an Express/SSR server.js that serves the built frontend AND your /api routes), Shipyard can run it instead of serving plain files — no separate backend, no CORS, same origin.
  • Turn it on at create time (“This app runs its own Node server”) or any time under Project → Settings → Runtime configuration → Runtime type = “Node server”. Shipyard then builds the frontend (npm run build) and runs your start command (npm start → node server.js), health-checks it, and routes traffic to it.
  • Port: Shipyard assigns the port and injects it as PORT — your server MUST listen on process.env.PORT (and bind 0.0.0.0). There are no cross-app port conflicts (each app runs in its own isolated container), so you don’t pick a port. Set a health check path (e.g. /health) that returns 200.
  • Result: your relative /api/submit (or /api/submissions) calls work in production exactly like they do locally, because the same Express process serves both.

Node.js service

  • For Express / Fastify / Nest and other long-running servers. Runs in an isolated container with zero-downtime promotion.
  • Install: npm ci. Build: npm run build (if you compile). Start command: e.g. node dist/main.js. Port: the port your server listens on.
  • Health check path: a route returning 200 when ready (e.g. /health). A new release only receives traffic after it passes the check.

Python service

  • For Flask / FastAPI / Django and other Python web servers. Runs in an isolated Python container with zero-downtime promotion (same model as Node).
  • Install runs in a venv so the app is self-contained: python -m venv .venv && .venv/bin/pip install -r requirements.txt (Shipyard puts .venv/bin on PATH at runtime). Build command is usually empty.
  • Start command: gunicorn app:app --bind 0.0.0.0:$PORT (Flask/WSGI), uvicorn main:app --host 0.0.0.0 --port $PORT (FastAPI), or python manage.py runserver 0.0.0.0:$PORT (Django). Shipyard assigns the port and injects it as PORT — bind to it.
  • Health check path: a route returning 200 when ready (e.g. /health). The auto-detector picks the framework + start command from your repo; you can override everything in Settings.