Skip to Content
Deploy

Deploy

Production deployment uses Docker Compose with Traefik as a reverse proxy. The stack is defined in docker-compose.prod.yml at the repository root.

Services

The production stack runs five services:

ServiceImageRole
bugzkit-apiwhile1618/bugzkit-api:1.0.0Spring Boot API
bugzkit-uiwhile1618/bugzkit-ui:1.0.0SvelteKit frontend (Node)
postgrespostgres:17.2-alpineDatabase
redisredis:7.4.1Token storage and cache
traefiktraefik:v3.3Reverse proxy with automatic HTTPS

Routing

Traefik handles TLS termination and routing:

  • bugzkit.com and www.bugzkit.com route to the UI on port 5173.
  • api.bugzkit.com routes to the API on port 8080.
  • HTTP is automatically redirected to HTTPS.
  • Certificates are obtained via Let’s Encrypt (HTTP challenge).

Replace the domain names in docker-compose.prod.yml with your own.

Secrets

Sensitive values are stored as plain files in a secrets/ directory next to the compose file. Docker Compose mounts each file into the container at /run/secrets/<name>. Spring Boot reads them via spring.config.import: configtree:/run/secrets/.

Create the directory and one file per secret before deploying:

mkdir -m 700 secrets echo -n "your_password" > secrets/postgres_password echo -n "your_password" > secrets/redis_password echo -n "your_secret" > secrets/jwt_secret # min 32 chars echo -n "your_password" > secrets/smtp_password echo -n "your_password" > secrets/user_password echo -n "your_client_id" > secrets/google_client_id echo -n "your_client_secret" > secrets/google_client_secret chmod 400 secrets/*

The secrets/ directory is listed in .gitignore and must never be committed.

VPS Initialization

scripts/init-vps.sh automates the full server setup on a fresh Debian 12 (DigitalOcean) droplet. Run it as root:

bash init-vps.sh

It covers: system hardening, SSH lockdown, UFW firewall, Fail2ban, automatic security updates, Docker CE, and writing all secret files with correct permissions.

Deploying via GitHub Actions

The deploy.yml workflow deploys the stack via SSH. It is triggered manually and only runs on the master branch. It requires the following repository secrets:

  • HOST: The server address.
  • DEPLOY_USER: SSH user on the server.
  • DEPLOY_SSH_PRIVATE_KEY: SSH private key for authentication.

On each run the workflow copies docker-compose.prod.yml from the repository to the server, then pulls updated images and restarts only the affected containers.

Manual Deploy

cd ~/bugzkit docker compose -f docker-compose.prod.yml pull docker compose -f docker-compose.prod.yml up -d --remove-orphans

Client IP Resolution

The production profile is configured for a Cloudflare + Traefik setup out of the box:

  • server.forward-headers-strategy: native — Tomcat resolves the real client IP from X-Forwarded-For set by Traefik.
  • server.client-ip-header: CF-Connecting-IP — the API reads Cloudflare’s header for the real visitor IP, which cannot be spoofed by clients.

If you use a different CDN or no CDN at all, override the CLIENT_IP_HEADER environment variable:

SetupCLIENT_IP_HEADER
Cloudflare (default)CF-Connecting-IP
FastlyFastly-Client-IP
AkamaiTrue-Client-IP
nginx (real_ip module)X-Real-IP
Traefik only (no CDN)(unset)

When unset, the server falls back to getRemoteAddr() resolved by Tomcat from X-Forwarded-For, which is correct for a Traefik-only deployment.

Last updated on