Tutorial: 7. Shipping it

Tutorial

7. Shipping it

The app works. In this last step you'll build it for production and run it the way a server would, first directly and then in Docker.

Build and run

Stop the dev server, then:

bun run build
NODE_ENV=production bun run start -- --no-build

sigil build compiles every route for the browser, once for hydrating server-rendered pages and once for rendering pages from scratch after client-side navigation, and writes the bundles to public/__grimoire__/. sigil start runs the production server on the configured port. It builds first unless you pass --no-build, which is what you want when the build already happened, as it will in Docker.

The production server is a small coordinator that hands requests to worker processes. By default there's one worker, on the port after yours (3001 when you serve on 3000), and a crashed worker is restarted automatically. Deploying covers running more workers.

Open http://localhost:3000: it's the same app, minus the dev server's live reload.

Listen on every interface

The config listens on localhost by default, which is right on your machine and wrong in a container. Let the environment decide, in sigil.config.ts:

sigil.config.ts
import type { GrimoireConfig } from "@sigil-dev/grimoire";

export default {
	port: Number(process.env.PORT ?? 3000),
	host: process.env.HOST ?? "localhost",
	routes: "src/routes",
} satisfies GrimoireConfig;

While you're there, let the database path come from the environment too, so a container can keep it on a volume. In src/lib/server/db.ts:

src/lib/server/db.ts (first lines)
const db = new Database(process.env.DB_PATH ?? "books.sqlite", { create: true });

Docker

Add a Dockerfile:

Dockerfile
FROM oven/bun:1-alpine
WORKDIR /app

COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

COPY . .
RUN bun run build

ENV NODE_ENV=production HOST=0.0.0.0 DB_PATH=/data/books.sqlite
EXPOSE 3000
CMD ["bun", "run", "start", "--", "--no-build"]

And a .dockerignore, so local build output and the database stay out of the image:

.dockerignore
node_modules
.grimoire
public/__grimoire__
books.sqlite

Build and run it:

docker build -t reading-list .
docker run -p 3000:3000 -v reading-list-data:/data reading-list

The named volume keeps the database across container restarts. Put the container behind a reverse proxy that terminates TLS, and it's deployed.

Where to go next

You've now used most of Sigil. From here:

  • Guides walk through common tasks: sharing state between components, sign-in, WebSockets, streaming slow data.
  • Reference has the full detail on everything in this tutorial.
  • Internals shows what the compiler does to your components, if you want to know how it works.

Edit this page