Deployment
The backend is packaged as a Docker image and can be deployed to any container platform. Three deployment targets are officially supported.
Docker Image
The project uses a multi-stage Dockerfile:
| Stage | Base Image | Purpose |
|---|---|---|
| Build | gradle:8.5-jdk21 | Compile and package the JAR |
| Runtime | eclipse-temurin:21-alpine | Minimal runtime image |
The runtime runs as a non-root user (spring:spring) for security.
Build the Image
docker build -t association-app-backend:latest .
Health Check
Docker is configured with a built-in health check:
GET /actuator/health
Docker Compose (Local / Staging)
The repository includes two compose files:
| File | Use |
|---|---|
docker-compose.yml | Local development (with MailHog) |
docker-compose.prod.yml | Production-like setup |
Services
services:
postgres: # PostgreSQL 16 — port 5432
mailhog: # Email testing — SMTP 1025, Web UI 8025
app: # Spring Boot API — port 8080
Start
# Development
docker-compose up --build
# Production-like
docker-compose -f docker-compose.prod.yml up --build
Persistent Data
PostgreSQL data is stored in a named volume (postgres-data) so it survives container restarts. To wipe and start fresh:
docker-compose down -v
Spring Profiles
Set the active profile via the SPRING_PROFILES_ACTIVE environment variable.
| Profile | When to use | Notes |
|---|---|---|
local | Native dev (localhost DB) | Default for ./gradlew bootRun |
docker | Docker Compose | Uses postgres hostname |
dokploy | Dokploy PaaS | All config via env vars |
cloudrun | Google Cloud Run | Uses Cloud SQL socket factory |
production | Any production target | Full security hardening |
Deployment Targets
1. Dokploy
Dokploy is a self-hosted PaaS that deploys Docker containers from a Git repository.
Setup:
- Create a new application in Dokploy
- Point it to the repository
- Set
Dockerfileas the build method - Add all environment variables in the Dokploy dashboard (see Getting Started → Environment Variables)
- Set
SPRING_PROFILES_ACTIVE=dokploy
The app will auto-deploy on every push to the configured branch.
Database:
Dokploy can manage a shared PostgreSQL instance. Point DB_HOST, DB_NAME, DB_USER, DB_PASSWORD at that instance.
A sample env file is available at .env.dokploy.example in the repository.
2. Google Cloud Run
Cloud Run runs stateless containers on Google's infrastructure with automatic scaling.
Requirements:
- Google Cloud project with Cloud Run and Cloud SQL APIs enabled
- Cloud SQL PostgreSQL 16 instance
- Service account with Cloud SQL Client role
Setup:
- Build and push the image to Google Container Registry or Artifact Registry:
gcloud builds submit --tag gcr.io/YOUR_PROJECT/association-app-backend
- Deploy to Cloud Run:
gcloud run deploy association-app-backend \
--image gcr.io/YOUR_PROJECT/association-app-backend \
--platform managed \
--region us-central1 \
--add-cloudsql-instances YOUR_PROJECT:REGION:INSTANCE_NAME \
--set-env-vars SPRING_PROFILES_ACTIVE=cloudrun \
--set-env-vars DB_HOST=/cloudsql/YOUR_PROJECT:REGION:INSTANCE_NAME \
--set-env-vars DB_NAME=associationapp \
--set-env-vars DB_USER=associationapp \
--set-env-vars DB_PASSWORD=your-password \
--set-secrets JWT_SECRET=jwt-secret:latest
- Set
SPRING_PROFILES_ACTIVE=cloudrun— this activates the Cloud SQL socket factory which connects via Unix socket instead of TCP.
See docs/CLOUD_RUN_TROUBLESHOOTING.md in the backend repository for common Cloud Run deployment issues and fixes.
3. Any Docker Host (VPS / Bare Metal)
# Pull image (if using a registry)
docker pull your-registry/association-app-backend:latest
# Or build locally
docker build -t association-app-backend:latest .
# Run
docker run -d \
--name association-app \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=production \
-e DB_HOST=your-db-host \
-e DB_NAME=associationapp \
-e DB_USER=associationapp \
-e DB_PASSWORD=your-password \
-e JWT_SECRET=your-jwt-secret \
-e FRONTEND_URL=https://app.example.com \
-e CORS_ALLOWED_ORIGINS=https://app.example.com \
association-app-backend:latest
Database Migrations
Flyway runs all pending migrations automatically at startup. No manual migration step is needed.
If you are running multiple instances (horizontal scaling), ensure only one instance applies migrations at a time, or use a migration lock. Spring Boot + Flyway handles this correctly with the default table-level lock.
Reverse Proxy (Nginx / Caddy)
The app listens on port 8080. Put a reverse proxy in front for TLS termination.
Nginx example:
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Production Checklist
-
JWT_SECRETis a strong random value (openssl rand -base64 64) -
SETTINGS_ENCRYPTION_KEYis set (openssl rand -base64 32) - Database password is strong and not the default
-
SPRING_PROFILES_ACTIVE=production -
CORS_ALLOWED_ORIGINSis set to your frontend domains only - Email is configured and
EMAIL_ENABLED=true - Storage provider is configured (not local filesystem for production)
- HTTPS / TLS is terminated at the reverse proxy
- Health check endpoint is monitored (
/actuator/health) - Database backups are configured