Docker
Run Laju Go in a Docker container — one command to build and start, everything isolated from the host. The multi-stage image prebuilds the Svelte frontend, compiles the Go binary with CGO enabled, and ships a minimal Alpine runtime. You’ll need a reverse proxy for HTTPS.
Prerequisites
Section titled “Prerequisites”- Docker and Docker Compose installed on your server (install guide).
- SSH access to the server.
- Your code on GitHub — clone your app repo (replace
<your-repo-url>with your repo URL).
git clone <your-repo-url>cd laju-goThe rest of this guide runs from inside the cloned repo directory.
Quick start
Section titled “Quick start”docker compose up -d --build- Multi-stage
Dockerfilewith three stages:node:22-alpinebuilds the frontend,golang:1.26-alpinecompiles the Go binary with CGO enabled (gcc musl-dev sqlite-static), andalpine:3.20is the minimal runtime. ./dataand./storagevolumes keep the SQLite database and uploads across restarts; the healthcheck hits/health.- The container listens on port 8080 (
EXPOSE 8080).
Environment variables
Section titled “Environment variables”Laju Go reads env from .env at startup — same file as bare-metal.
Docker Compose injects it into the container via env_file: .env in
docker-compose.yml:
services: laju-go: build: . ports: - "8080:8080" env_file: .env environment: APP_ENV: production APP_URL: ${APP_URL:-http://localhost:8080} volumes: - ./data:/app/data # persistent SQLite database - ./storage:/app/storage # persistent uploads restart: unless-stopped healthcheck: test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:8080/health"] interval: 30s timeout: 5s retries: 3Step by step
Section titled “Step by step”-
Copy the example file in the project root (on the host, not inside the container):
Terminal window cp .env.example .env -
Edit
.env— set the values for your deployment. The minimum you need to change:APP_PORT=8080APP_ENV=productionAPP_URL=https://your-domain.comDB_PATH=/app/data/app.dbSESSION_SECRET=<random 32-char string>DB_PATHmust point inside the./datavolume (/app/data/...) so the SQLite file survives container restarts. See Configuration for the full env table. -
.envis never baked into the image..dockerignoreexcludes it — the.envfile stays on the host and is injected at runtime viaenv_file. This means you can rebuild the image without leaking secrets, and different environments (staging, production) just use different.envfiles on the host. -
APP_ENVandAPP_URLare set indocker-compose.ymlunderenvironment:, not.env. Compose’senvironment:block overridesenv_filevalues, so these two are always correct for the container even if your.envhas different values for local dev. If you need to overrideAPP_URL, set it in.envor pass it at build time:Terminal window APP_URL=https://your-domain.com docker compose up -d --build -
Build and start:
Terminal window docker compose up -d --buildThe first build takes ~60s (installs npm deps, builds Vite assets, downloads Go modules, compiles with CGO). Subsequent builds are faster thanks to Docker layer caching.
-
Check it’s running:
Terminal window docker compose ps # should show "healthy"docker compose logs -f # tail logscurl http://localhost:8080/health
Changing env later
Section titled “Changing env later”Edit .env on the host, then restart the container (no rebuild needed —
the image doesn’t change, only the runtime env):
docker compose restartIf you changed APP_URL in docker-compose.yml itself, or changed the
compose file structure, use up instead:
docker compose up -dRoutine updates
Section titled “Routine updates”After you push code to GitHub, pull and rebuild on the server:
git pulldocker compose up -d --buildcurl http://localhost:8080/health # → {"status":"ok",...}The ./data and ./storage volumes persist across rebuilds — your
SQLite database and uploads are not affected.
Reverse proxy
Section titled “Reverse proxy”The container listens on port 8080 (already mapped in docker-compose.yml).
For HTTPS, put a reverse proxy in front — same setup as bare-metal:
If your proxy runs on the same server (Caddy, Nginx, Cloudflare Tunnel),
change the port mapping in docker-compose.yml to "127.0.0.1:8080:8080"
so port 8080 is not exposed publicly.