Imouto

Dockerise Imouto for Local Server Deployment

Containerised the imouto SvelteKit frontend with a multi-stage Docker build (Bun builder plus Caddy static server) and wired it into a docker-compose setup accessible at localhost/imouto.

6 Phases
6 Tasks
1 Days

The Problem

The imouto frontend is a statically prerendered SvelteKit app -- no Node.js process needed at runtime, just a folder of HTML, CSS, and JS files. But I wanted it running on my local home server, accessible at http://localhost/imouto, routed by Caddy alongside other services. That meant solving three things: configuring SvelteKit to know it lives at a sub-path, building a Docker image that produces only the static output, and setting up Caddy to handle the prefix-stripping correctly.

What I Built

SvelteKit Base Path

The first step was telling SvelteKit it is being served from /imouto, not /. Without paths.base set in svelte.config.js, every asset reference in the built HTML points to /_app/... instead of /imouto/_app/.... A one-line config change, but it affects every internal link and asset URL in the build output.

Multi-Stage Dockerfile

The Docker build uses two stages:

  • Stage 1 (builder): Uses oven/bun:1-alpine to match the project's existing bun.lock. Installs dependencies, copies in dataset.json and matches.json (generated by the Python pipeline -- not committed to git), and runs bun run build. The SvelteKit build imports these JSON files at build time via static imports in data.ts.
  • Stage 2 (serve): Uses caddy:2-alpine. Copies only the built static files from stage 1. No Node, no Bun, no source code in the final image -- roughly 50MB.

Caddy Routing

The Caddyfile uses handle_path /imouto/* which strips the prefix before looking up files. A request for /imouto/occupation/US:29-1021.00 maps to /srv/imouto/occupation/US:29-1021.00/index.html. The try_files directive handles the SvelteKit prerendered routes and falls back to index.html for edge cases.

Binding to :80 rather than localhost means the server is accessible from other devices on the same LAN -- useful for testing on a phone without deploying anywhere.

Build Context Hygiene

A .dockerignore keeps the build fast and avoids leaking anything sensitive. It excludes the Python virtual environment, node_modules (rebuilt inside Docker), the cache/ directory with confidential AU data files, .env files, and the .plans/ documentation.

What is Next

The container runs and serves the static site, but it is on its own Docker network. The next step is wiring it into the Motivka Caddy reverse proxy so https://localhost/imouto resolves through the shared TLS endpoint alongside all other local services.

Features Delivered

SvelteKit Base Path

  • Configured paths.base to /imouto for sub-path serving

Docker Build

  • Multi-stage Dockerfile with Bun builder and Caddy static server
  • Build context hygiene via .dockerignore
  • Final image around 50MB with no build tools

Routing and Compose

  • Caddy handle_path prefix-stripping for /imouto sub-path
  • docker-compose.yml single-service definition
  • LAN-accessible on port 80

Key Decisions

  • Bun over npm in Docker -- repo already has bun.lock, avoids lockfile conflicts
  • Caddy over nginx -- handle_path does prefix-stripping in 3 lines vs nginx location/alias/rewrite complexity
  • Multi-stage build -- keeps final image small and ensures no build secrets are embedded
  • :80 not localhost in Caddyfile -- accessible from LAN devices