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:
| Service | Image | Role |
|---|---|---|
| bugzkit-api | while1618/bugzkit-api:1.0.0 | Spring Boot API |
| bugzkit-ui | while1618/bugzkit-ui:1.0.0 | SvelteKit frontend (Node) |
| postgres | postgres:17.2-alpine | Database |
| redis | redis:7.4.1 | Token storage and cache |
| traefik | traefik:v3.3 | Reverse proxy with automatic HTTPS |
Routing
Traefik handles TLS termination and routing:
bugzkit.comandwww.bugzkit.comroute to the UI on port 5173.api.bugzkit.comroutes 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 bugzkitClient 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 fromX-Forwarded-Forset 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:
| Setup | CLIENT_IP_HEADER |
|---|---|
| Cloudflare (default) | CF-Connecting-IP |
| Fastly | Fastly-Client-IP |
| Akamai | True-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.