Docker¶
LOOPZE ships with a multi-stage Dockerfile and a docker-compose.yml
under demo/. The Dockerfile builds the Vue 3 frontend, embeds it into
the Go binary, and produces a minimal Alpine-based runtime image.
Quickstart¶
From the repo root:
make docker-up # via Makefile, simplest
# — or equivalently:
cd demo && docker compose up -d # compose handles the build context
The first build takes ~1–2 minutes (frontend + Go binary). Subsequent builds reuse Docker's BuildKit cache and complete in seconds.
Build context must be the repo root
The Dockerfile lives in demo/ but copies files that live one
level up (go.mod, frontend/, internal/, …). A bare
docker build . from inside demo/ will fail with go.sum: not
found. Use make docker-build, or pass the parent directory
explicitly: docker build -f Dockerfile -t loopze-edge:local ...
Open http://localhost:1880 — on first launch
you'll be prompted to create the initial admin account. State persists
in demo/data/ on the host.
docker compose logs -f loopze # follow logs
docker compose down # stop (state in ./data is kept)
docker compose down -v # stop AND wipe state
Image layout¶
The Dockerfile uses three stages:
| Stage | Base image | Purpose |
|---|---|---|
frontend |
node:20-alpine |
Build the Vue 3 editor (vite build). |
backend |
golang:1.25-alpine |
Compile the Go binary, embedding the frontend via go:embed. |
| runtime | alpine:3.20 |
Minimal runtime — ca-certificates, tzdata, wget for the healthcheck. |
The final image is around 90 MB. It's not smaller because the frontend ships with the full Monaco editor (~4 MB pre-gzip) which is embedded into the binary.
The container runs as the unprivileged user loopze (UID/GID 1000).
Configuration¶
Every command-line flag is mirrored as a LOOPZE_* environment
variable — set them in docker-compose.yml:
services:
loopze:
environment:
LOOPZE_PORT: 1880
LOOPZE_LOG_LEVEL: info
LOOPZE_AUTH_DISABLE: "false"
# LOOPZE_BASE_PATH: /loopze
# LOOPZE_TRUSTED_PROXIES: 10.0.0.0/8,172.16.0.0/12
See the CLI reference for the full list.
Persistence¶
The image presets LOOPZE_DATA_DIR=/data and declares /data as a
volume. The compose file bind-mounts ./data on the host:
Inside the volume:
data/
├── flows.json # the flow graph
├── credentials.json # AES-256-GCM-encrypted secrets + stored certs
├── users.json # local user accounts (Argon2id hashes)
├── loopze.session.key # session-cookie HMAC key
└── *.key # credential encryption key
Backup the whole data directory
Without *.key you cannot decrypt credentials.json. Back up the
directory as a unit — partial backups can leave you with encrypted
secrets you can't read.
If you bind-mount from the host, ensure ownership permits the container
UID/GID (chown -R 1000:1000 ./data works in most cases).
File-source certificates¶
The cert store supports Source: file
entries that point at PEM files outside credentials.json. In
containerised deployments mount the cert directory read-only inside
the container at the same absolute path you stored:
services:
loopze:
image: ghcr.io/loopzedev/loopze-edge:latest
volumes:
- ./data:/data
- /etc/loopze/certs:/etc/loopze/certs:ro
Cert-manager / Let's Encrypt rotations swap the file in place; LOOPZE re-reads the contents at every connection init, so no container restart is required.
Healthcheck¶
The image runs a periodic check against /health:
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=15s \
CMD wget -q -O- http://127.0.0.1:1880/health || exit 1
docker ps shows (healthy) in the STATUS column once the check
passes.
Building tagged images¶
The build accepts VERSION, COMMIT and BUILD_TIME as build-args.
For a tagged release:
docker build \
--build-arg VERSION=v0.5.1 \
--build-arg COMMIT=$(git rev-parse --short HEAD) \
--build-arg BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ') \
-f demo/Dockerfile -t loopze-edge:0.5.1 .
The values appear in loopze --version output and in the startup banner.
Multi-arch builds¶
For Raspberry Pi and other ARM targets:
docker buildx build \
--platform linux/amd64,linux/arm64 \
-f demo/Dockerfile \
-t myregistry/loopze:0.5.1 \
--push .
Production notes¶
- TLS — terminate TLS in front (Caddy / nginx / Traefik). LOOPZE serves plain HTTP and is not intended to face the public internet directly.
- Reverse proxy under a subpath — set
LOOPZE_BASE_PATH=/loopzeand point your proxy athttp://loopze:1880/. The frontend's<base>tag is rewritten at request time, so no rebuild is needed. - Trusted proxies — set
LOOPZE_TRUSTED_PROXIES(CIDRs) so the real client IP is honored fromX-Forwarded-For. - Pin image versions — for production, pull from your registry by tag rather than rebuilding from source on every deploy.