Skip to content

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.

  • 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).
Terminal window
git clone <your-repo-url>
cd laju-go

The rest of this guide runs from inside the cloned repo directory.

Terminal window
docker compose up -d --build
  • Multi-stage Dockerfile with three stages: node:22-alpine builds the frontend, golang:1.26-alpine compiles the Go binary with CGO enabled (gcc musl-dev sqlite-static), and alpine:3.20 is the minimal runtime.
  • ./data and ./storage volumes keep the SQLite database and uploads across restarts; the healthcheck hits /health.
  • The container listens on port 8080 (EXPOSE 8080).

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: 3
  1. Copy the example file in the project root (on the host, not inside the container):

    Terminal window
    cp .env.example .env
  2. Edit .env — set the values for your deployment. The minimum you need to change:

    APP_PORT=8080
    APP_ENV=production
    APP_URL=https://your-domain.com
    DB_PATH=/app/data/app.db
    SESSION_SECRET=<random 32-char string>

    DB_PATH must point inside the ./data volume (/app/data/...) so the SQLite file survives container restarts. See Configuration for the full env table.

  3. .env is never baked into the image. .dockerignore excludes it — the .env file stays on the host and is injected at runtime via env_file. This means you can rebuild the image without leaking secrets, and different environments (staging, production) just use different .env files on the host.

  4. APP_ENV and APP_URL are set in docker-compose.yml under environment:, not .env. Compose’s environment: block overrides env_file values, so these two are always correct for the container even if your .env has different values for local dev. If you need to override APP_URL, set it in .env or pass it at build time:

    Terminal window
    APP_URL=https://your-domain.com docker compose up -d --build
  5. Build and start:

    Terminal window
    docker compose up -d --build

    The 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.

  6. Check it’s running:

    Terminal window
    docker compose ps # should show "healthy"
    docker compose logs -f # tail logs
    curl http://localhost:8080/health

Edit .env on the host, then restart the container (no rebuild needed — the image doesn’t change, only the runtime env):

Terminal window
docker compose restart

If you changed APP_URL in docker-compose.yml itself, or changed the compose file structure, use up instead:

Terminal window
docker compose up -d

After you push code to GitHub, pull and rebuild on the server:

Terminal window
git pull
docker compose up -d --build
curl http://localhost:8080/health # → {"status":"ok",...}

The ./data and ./storage volumes persist across rebuilds — your SQLite database and uploads are not affected.

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:

Reverse proxy guide

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.