Skip to content

Deployment

Local hot-reload development

Run the backend and frontend in separate terminals:

sh
just backend
just frontend
  • Go listens on http://localhost:8080.
  • Vite listens on http://localhost:5173.
  • Vite proxies /api, /events, and /ws to Go.
  • just backend uses the persistent, gitignored backend/data/dev.db SQLite database.
  • STATIC_DIR stays unset because Vite serves frontend files.

Documentation site

The VitePress documentation is an isolated package. Install its dependencies and start the local development server with:

sh
npm ci --prefix docs
just docs

Open http://localhost:5174. Production output and a local preview are available through just docs-build and just docs-preview.

Build and serve the production-shaped static container with:

sh
just docs-container-build
just docs-container-up

The Compose command enables the profile-gated docs service at the generated worktree hostname with -docs appended. A normal just worktree-up starts only the application service.

The site uses / as its VitePress base and is intended for a dedicated documentation hostname. The deployment platform or reverse proxy should provide that hostname and TLS.

Single-container runtime

Build and run locally with either Docker directly or the worktree-aware Compose wrapper:

sh
docker build -f deployment/Dockerfile -t pointing:local .
docker run --rm -p 3000:8080 -v backlog-data:/data pointing:local

# production-shaped worktree stack behind the shared proxy
just worktree-up

The multi-stage build contains dedicated test stages:

  1. frontend-deps installs locked Node dependencies.
  2. frontend-test runs Svelte and TypeScript checks.
  3. frontend-build creates the static SvelteKit site.
  4. backend-deps downloads locked Go modules.
  5. backend-test runs Go tests and go vet.
  6. test is an aggregate target that succeeds only after both test stages complete.
  7. backend-build creates the static Go binary.
  8. The distroless runtime receives only the binary, frontend files, and writable data directory.

Run only the containerized validation target with:

sh
docker build -f deployment/Dockerfile --target test -t pointing:test .

The final image runs one non-root Go process on port 8080. It serves:

PathFunction
/assets/*Hashed frontend assets with immutable caching
/Prerendered SvelteKit page with no-cache
/room/*SvelteKit client fallback with no-cache
/api/*JSON API
/events/*SSE streams
/ws/*WebSocket connections

The default Compose profile starts exactly one app container and routes its generated pointing.localhost hostname through Traefik.

Concurrent Git worktrees

just worktree-up starts the machine-wide Traefik project if needed, then starts an isolated Compose project for the current Git worktree. DNS-safe branch names remain unchanged. Names that require normalization or truncation receive a stable eight-character worktree hash, preventing branches such as feature/settings and feature-settings from sharing containers or volumes. An explicit recipe argument or POINTING_WORKTREE overrides the readable portion; detached HEADs fall back to the worktree directory name. Use just worktree-url to print the exact hostname.

pointing.localhost is used because its subdomains resolve to loopback without /etc/hosts, dnsmasq, or .local mDNS conflicts. Port 80 must be available. The local setup is HTTP-only and requires Docker Compose 2.22 or newer for worktree-watch.

Useful commands are:

sh
just worktree-up
just worktree-watch      # rebuild on source changes; Ctrl-C stops the stack
just worktree-url
just worktree-status
just worktree-logs
just worktree-observability-up
just worktree-down       # preserve this worktree's volumes
just worktree-destroy    # delete this worktree's volumes
just proxy-down          # after every worktree is down

Only Traefik and its pointing-proxy network are shared. Each worktree has independent app and optional PostgreSQL containers, images, default networks, and named volumes. The read-only Docker socket mount gives Traefik broad visibility into local Docker metadata and is intended for development machines.

Observability

The application writes structured JSON logs to standard output. LOG_LEVEL controls verbosity and defaults to info. Request logs use route templates and deliberately omit query strings, room codes, participant IDs, request bodies, names, topics, and votes.

Traces, application metrics, and Go runtime metrics are exported over OTLP/HTTP when OTEL_EXPORTER_OTLP_ENDPOINT is set. Export is disabled when the endpoint is empty, so the normal application profile does not require an observability backend. Common settings include:

sh
OTEL_EXPORTER_OTLP_ENDPOINT=https://collector.example.com:4318
OTEL_SERVICE_NAME=backlog-holdem
DEPLOYMENT_ENVIRONMENT=production
OTEL_METRIC_EXPORT_INTERVAL=60000
OTEL_TRACES_SAMPLER=parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1

For local inspection, start the profile-gated Grafana LGTM development image:

sh
just worktree-observability-up

This starts the application with OTLP export enabled and 100% trace sampling. The command prints the worktree-specific Grafana URL ending in -obs.pointing.localhost. LGTM is intended for development and testing, not as the production telemetry store. Application logs remain available through just worktree-logs; production log collection should read container standard output.

The initial application metrics cover HTTP request rate and duration, room command outcomes and duration, active SSE and WebSocket connections, connection duration, dropped room updates, repository operations, and the Go runtime. Labels are limited to route templates, methods, status codes, command names, transports, outcomes, and database drivers. Traces follow API requests and WebSocket commands into repository operations without recording room or participant data.

Persistent storage

SQLite writes /data/data.db in the backlog-data volume. Preserve or back up this volume when replacing containers.

The server migrates this database at startup by default. The recommended production rollout applies migrations once before replacing application instances:

sh
DATABASE_AUTO_MIGRATE=false just worktree-migrate
DATABASE_AUTO_MIGRATE=false just worktree-up

Keep DATABASE_AUTO_MIGRATE=true for the simpler single-instance workflow. Set it to false when a deployment job runs /app/migrate up; this avoids every application replica attempting deployment work. Back up persistent data before destructive forward migrations.

PostgreSQL remains an external alternative. Supply DATABASE_DRIVER=postgres and DATABASE_URL, or use the optional local profile:

sh
COMPOSE_PROFILES=postgres DATABASE_DRIVER=postgres just worktree-up

That profile starts a second database container and is intentionally not the single-container SQLite topology.

GitLab Container Registry

.gitlab-ci.yml validates merge requests, branches, and tags. Image publication runs only for main and Git tags using GitLab-provided registry credentials.

The project registry path already ends in the repository name pointing. Published tags are:

Pipeline sourceTags
main$CI_COMMIT_SHA, latest
Git tag$CI_COMMIT_SHA, $CI_COMMIT_TAG

Pull an image with:

sh
docker login registry.gitlab.com
docker pull registry.gitlab.com/morgan.trench/pointing:latest

OCI labels capture the GitLab project URL, commit revision, and release/commit version.

The independently deployable documentation image is published under $CI_REGISTRY_IMAGE/docs with the same SHA, latest, and Git tag policy. For example:

sh
docker pull registry.gitlab.com/morgan.trench/pointing/docs:latest

Production considerations

The application image deliberately does not terminate TLS. Deploy it behind the ingress supplied by the target platform and ensure that ingress:

  • supports WebSocket upgrades;
  • does not buffer SSE responses;
  • permits long-lived connection idle times;
  • forwards the original scheme and host appropriately.

Before public exposure, also add authenticated sessions, request limits, backups, and observability. Keep one application replica until distributed events and optimistic concurrency are implemented.

Backlog Hold'em project documentation