Docker Deployment
Use Docker as the supported deployment contract today for published api and full Webstir workspaces.
Webstir is still experimental, but this is the one Bun deployment path the repo supports and tests end to end right now. Treat other deployment shapes as out of contract unless the docs start naming them explicitly.
Command Flow
From the workspace root:
webstir publish --workspace "$PWD"
docker build -t my-webstir-app .
docker run --rm -p 8080:8080 -v my-webstir-data:/app/data --env-file ./.env.production my-webstir-appDockerfile
Use the canonical Dockerfile and .dockerignore in the workspace root:
FROM oven/bun:1.3.11-alpine
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile --production
COPY . .
RUN mkdir -p /app/data
ENV NODE_ENV=production
ENV PORT=8080
EXPOSE 8080
CMD ["bun", "./node_modules/.bin/webstir-backend-deploy", "--workspace", "/app", "--port", "8080"]Runtime Contract
- The deploy reads what the app is from
build/published-layers.json, which publish writes, since the image carries nosrc/. An app recorded with pages whosedist/frontend/is missing fails at startup rather than serving only its API. - An app without pages exposes the published backend on the container port.
- An app with pages exposes one public port that serves
dist/frontend/**and proxies/api/*to the published backend. dist/frontend/**is only there for an app with pages; an app without pages builds the image without adisttree.- A page a view renders is answered only by that view. Its template under
dist/frontend/pages/is never served as a file, so a view'sauthcovers every way of asking for the page. - The single public port keeps the runtime probes available without a second sidecar port:
GET /healthzGET /readyzGET /metrics
/metricsreports request counts and timings;METRICS_ENABLED=offturns them off and it returns{ "enabled": false }./app/dataholds the SQLite database and stored files by default: mount a volume there so they outlive the container, or pointDATABASE_URLat Postgres andSTORAGE_URLat S3.- Migrations apply when the container starts; one that fails stops it with its file and error.
SESSION_SECRETis required in production. With sign-in, so areAPP_URL,EMAIL_URLandEMAIL_FROM. See Environment.- Only published apps with a server are in contract for this deploy path; an app without one publishes as a static site.
Stopping
A container is stopped with SIGTERM, by docker stop, a deploy or a restart. SIGINT is the same stop. The server then:
- Takes no new connections, and lets the requests in flight finish.
- Stops its jobs, waiting for one that is running. A queued job still running when the wait ends goes back in the queue at the next start; a scheduled one runs again when it is next due.
- Closes the database, taking a SQLite database's pending snapshot, and exits with
0. Closing has 10 seconds, orSHUTDOWN_TIMEOUTwhen that is longer; a database still in use after that, by a transaction that never ended, is left, and the server exits with1.
SHUTDOWN_TIMEOUT is how long each wait lasts, 4 seconds by default, read from the environment or the app's .env. In an app with pages the public server waits for its requests first and then the app server waits for its jobs, so a stop can take twice the setting, and then as long as closing the database takes. That fits the 10 seconds Docker allows before it kills a container, with a database that closes within 2. An app with slower requests, or a snapshot that takes longer to upload, raises both:
docker run --stop-timeout 30 -e SHUTDOWN_TIMEOUT=10 ...A stop signal sent to the whole process group, as Ctrl-C and systemd send it, is the same one stop. Sent a second time to webstir-backend-deploy, it stops the wait: every connection is closed, the app server is stopped at once without closing its database, and the command exits with 1.
When The App Server Dies
webstir-backend-deploy runs the app server as its own process. If that process ends without being asked to, by a crash or by being killed, the command closes its port and exits with 1. Run the container with a restart policy, such as --restart unless-stopped, so it starts again.
Canonical Source
- Repo source:
orchestrators/bun/resources/deployment/docker/** - Packaged copy:
orchestrators/bun/assets/deployment/docker/**