Skip to main content

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:

StageBase ImagePurpose
Buildgradle:8.5-jdk21Compile and package the JAR
Runtimeeclipse-temurin:21-alpineMinimal 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:

FileUse
docker-compose.ymlLocal development (with MailHog)
docker-compose.prod.ymlProduction-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.

ProfileWhen to useNotes
localNative dev (localhost DB)Default for ./gradlew bootRun
dockerDocker ComposeUses postgres hostname
dokployDokploy PaaSAll config via env vars
cloudrunGoogle Cloud RunUses Cloud SQL socket factory
productionAny production targetFull security hardening

Deployment Targets

1. Dokploy

Dokploy is a self-hosted PaaS that deploys Docker containers from a Git repository.

Setup:

  1. Create a new application in Dokploy
  2. Point it to the repository
  3. Set Dockerfile as the build method
  4. Add all environment variables in the Dokploy dashboard (see Getting Started → Environment Variables)
  5. 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:

  1. Build and push the image to Google Container Registry or Artifact Registry:
gcloud builds submit --tag gcr.io/YOUR_PROJECT/association-app-backend
  1. 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
  1. Set SPRING_PROFILES_ACTIVE=cloudrun — this activates the Cloud SQL socket factory which connects via Unix socket instead of TCP.
info

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.

caution

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_SECRET is a strong random value (openssl rand -base64 64)
  • SETTINGS_ENCRYPTION_KEY is set (openssl rand -base64 32)
  • Database password is strong and not the default
  • SPRING_PROFILES_ACTIVE=production
  • CORS_ALLOWED_ORIGINS is 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