Skip to Content
Deploy

Deploy

Production deployment uses Docker Swarm with Traefik as a reverse proxy. The stack is defined in docker-stack.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-stack.prod.yml with your own.

Secrets

Sensitive values are managed through Docker Swarm secrets instead of environment variables. You need to create these secrets before deploying:

echo "your_password" | docker secret create postgres_password - echo "your_password" | docker secret create redis_password - echo "your_secret" | docker secret create jwt_secret - echo "your_password" | docker secret create smtp_password - echo "your_password" | docker secret create user_password - echo "your_client_id" | docker secret create google_client_id - echo "your_client_secret" | docker secret create google_client_secret -

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.

Manual Deploy

To deploy manually on a Docker Swarm node:

docker stack deploy -c docker-stack.prod.yml bugzkit

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