forgejo-tickets/docs/deployment.md

4.1 KiB

Deployment

Local Development

  1. Copy the example environment file and edit it:

    cp .env.example .env
    
  2. Start PostgreSQL (e.g. via Docker):

    docker run -d --name postgres \
      -e POSTGRES_USER=user -e POSTGRES_PASSWORD=password -e POSTGRES_DB=forgejo_tickets \
      -p 5432:5432 postgres:16-alpine
    
  3. Run the application:

    make run
    

    This runs go run ./cmd/server, which auto-migrates the database on startup.

  4. For CSS development, run the Tailwind watcher in a separate terminal:

    make tailwind-watch
    

The public UI is at http://localhost:8080 and the admin panel at http://localhost:8081.

Makefile Targets

Target Description
build Compiles Tailwind CSS then builds the Go binary (forgejo-tickets)
run Runs the server directly with go run (no prior build needed)
test Runs all Go tests (go test ./...)
tailwind Compiles Tailwind CSS from web/static/css/input.css to web/static/css/output.css (minified)
tailwind-watch Runs Tailwind in watch mode for development
docker Builds the Docker image tagged with the git SHA and latest
docker-push Builds and pushes the Docker image to the registry
clean Removes the compiled binary and generated CSS

Docker

The Dockerfile uses a 3-stage build:

  1. Node/Tailwind stage (node:22-alpine) — Compiles Tailwind CSS.
  2. Go build stage (golang:1.23-alpine) — Downloads dependencies and compiles the Go binary with CGO_ENABLED=0.
  3. Runtime stage (alpine:3.20) — Minimal image with just the binary, templates, and static assets. Exposes ports 8080 and 8081.

Build and run:

make docker
docker run --env-file .env -p 8080:8080 -p 8081:8081 registry.ts.mattnite.net/forgejo-tickets:latest

Nomad

The production deployment uses Nomad with the job spec at infra/tickets/tickets.hcl.

Job Structure

  • Job: tickets (service type, datacenter dc1)
  • Group: tickets (count 1)
    • Two ports on the Tailscale host network: http (public) and admin
  • Task: server (Docker driver)

Environment Injection

Secrets are stored as Nomad variables at the path nomad/jobs/tickets and injected via a template block:

  • DATABASE_URL, SESSION_SECRET
  • FORGEJO_URL, FORGEJO_API_TOKEN
  • POSTMARK_SERVER_TOKEN
  • GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
  • MICROSOFT_CLIENT_ID, MICROSOFT_CLIENT_SECRET
  • APPLE_CLIENT_ID, APPLE_TEAM_ID, APPLE_KEY_ID

Non-secret configuration is set directly in the env block:

  • PUBLIC_ADDR and ADMIN_ADDR use Nomad's dynamic port variables
  • BASE_URL is the public-facing URL
  • POSTMARK_FROM_EMAIL is the sender address
  • TAILSCALE_ALLOWED_USERS controls admin access

Traefik Routing

Two Nomad services are registered with Traefik tags:

Service Host Purpose
tickets tickets.ts.mattnite.net Public UI (internal Traefik, TLS via Let's Encrypt)
tickets-admin tickets-admin.ts.mattnite.net Admin panel (internal Traefik, TLS via Let's Encrypt)

Both use the websecure entrypoint with automatic TLS certificate resolution.

Health Checks

The public service registers an HTTP health check at GET /health with a 10-second interval and 30-second timeout.

Logging

Docker logging is configured to ship to Loki at loki.ts.mattnite.net with job name and allocation ID as labels.

Resources

  • CPU: 200 MHz
  • Memory: 256 MB

Database

The application uses PostgreSQL with GORM as the ORM. On startup, database.RunMigrations() calls models.AutoMigrate() which:

  1. Creates PostgreSQL enum types (ticket_status, token_type) if they don't exist
  2. Auto-migrates all model tables (User, OAuthAccount, Session, Repo, Ticket, TicketComment, EmailToken)
  3. Creates composite and partial unique indexes

No separate migration tool is needed — the schema is managed entirely through GORM auto-migration.

See Configuration for all environment variables and Forgejo Integration for webhook URL setup.