Skip to content

Docker and persistence

The generated consumer template includes a multi-stage Dockerfile for the supported long-lived Node deployment. It builds the Next.js standalone output, installs Chromium with Linux dependencies, runs as the node user, and starts through tini.

From a generated project containing the canonical Dockerfile, pnpm-lock.yaml, and pnpm-workspace.yaml:

Terminal window
docker build --tag framekit-app .
docker volume create framekit-data
docker run --detach \
--publish 3000:3000 \
--env FRAMEKIT_AUTH_ENABLED=false \
--mount type=volume,source=framekit-data,target=/data \
--name framekit-app \
framekit-app

This explicit open-mode example needs no login, user, token, or SQLite database. For an authenticated private deployment, pass the switch and bootstrap secret explicitly instead:

Terminal window
docker run --detach \
--publish 3000:3000 \
--env FRAMEKIT_AUTH_ENABLED=true \
--env FRAMEKIT_ADMIN_PASSWORD='replace-with-a-strong-password' \
--mount type=volume,source=framekit-data,target=/data \
--name framekit-app \
framekit-app

The template Dockerfile uses pnpm and requires both pnpm-lock.yaml and pnpm-workspace.yaml. If project creation omitted dependency installation, run pnpm install from the project root before docker build. Pass the remaining public configuration through the container environment as needed. Do not copy a secret .env file into the image. The container listens on port 3000 and the image sets /data/framekit.sqlite as the default FRAMEKIT_DATABASE_PATH; the runtime environment can override it.

Warning: Do not use .env.example unchanged as the container’s --env-file. Its FRAMEKIT_DATABASE_PATH=.framekit-data/framekit.sqlite overrides the Docker default /data/framekit.sqlite, so SQLite will not use the mounted /data volume. Omit that variable for the container or set it explicitly to /data/framekit.sqlite, and keep the other secrets in the runtime environment.

The build stages use Node 22 and pnpm 11.14.0. The builder runs pnpm build and checks for .framekit/next/standalone/server.js. The runner installs production dependencies and executes framekit browser install --with-deps.

The runner sets these image-level variables:

Variable Image value Meaning
NODE_ENV production Runtime mode.
HOSTNAME 0.0.0.0 Container bind hostname.
PORT 3000 Container and renderer port.
PLAYWRIGHT_BROWSERS_PATH /ms-playwright Installed Chromium location.
FRAMEKIT_DATABASE_PATH /data/framekit.sqlite Default SQLite path set by the image; the runtime environment can override it.

NODE_ENV, HOSTNAME, and PLAYWRIGHT_BROWSERS_PATH are image operational settings, not public FrameKit configuration choices. The Dockerfile does not force authentication. Pass FRAMEKIT_AUTH_ENABLED explicitly at runtime; FRAMEKIT_ADMIN_PASSWORD and FRAMEKIT_ADMIN_USERNAME are used only when it is true, while image-host and render-limit values remain deployment environment values.

The final image creates /data, makes it writable by node, exposes port 3000, drops root privileges with USER node, and uses /usr/bin/tini -- as its entrypoint before node server.js.

Mount /data as durable storage when authentication is enabled and users, sessions, or API-token metadata must survive container replacement. Without a durable mount, the image’s prepared directory is part of the container filesystem and can be lost when the container is replaced. Ensure a bind mount or volume is writable by the node user. Open mode does not initialize SQLite.

SQLite persistence does not persist render jobs. Render jobs are held in process memory, have a 120-second TTL, and are intentionally unavailable after a process restart. A replacement container can recover the database-backed account and token state while an in-flight or test render job is gone.

For authenticated mode, after the first login and token creation, restart or replace the container while keeping the same /data volume. The account and session database should remain available. Switching back to open mode does not delete that state. A render job created before the process replacement should not be expected to remain available; submit a new image request instead.