Deployment Guide
This guide covers the complete deployment process for the eduID Wallet Matching Portal, from a quick-start local development setup using Docker Compose to production deployment considerations. The portal consists of multiple interconnected services that must be configured and orchestrated correctly to function as a cohesive system.
Prerequisites
Before deploying the portal, ensure the following tools and runtimes are available in your environment:
| Prerequisite | Version | Purpose |
|---|---|---|
| Docker | 24+ | Container runtime for all services |
| Docker Compose | v2.20+ | Service orchestration and dependency management |
| PostgreSQL | 15+ | Primary database (provided via Docker or external) |
| Node.js | 20+ | Portal frontend development and build (development only) |
| JDK | 21+ | Kotlin backend service development and build (development only) |
For local development, Docker and Docker Compose are the only strict requirements, as all other dependencies are encapsulated in the container images. Node.js and JDK are needed only if you intend to build the services from source rather than using pre-built images.
Quick Start
The fastest way to get the full portal running locally is through the provided Docker Compose configuration:
cd deploy/docker
docker compose up
This single command starts all services with their default development configurations. Once all containers are healthy, the following endpoints become available:
| Service | URL | Description |
|---|---|---|
| Portal (Frontend) | http://localhost:3000 | The web application that wallet holders interact with |
| STS (Security Token Service) | http://localhost:8092 | Issues and validates OAuth2/OIDC tokens |
| Auth Bridge | http://localhost:8090 | Identity matching, reconciliation, and binding management |
To start the services in the background (detached mode):
cd deploy/docker
docker compose up -d
To stop all services and remove the containers:
cd deploy/docker
docker compose down
To stop all services and also remove persistent volumes (this deletes all database data):
cd deploy/docker
docker compose down -v
Docker Compose Services
The Docker Compose configuration defines five services with explicit dependency relationships. The following table describes each service, its image, exposed port, and what it depends on:
| Service | Image | Port | Depends On | Description |
|---|---|---|---|---|
postgres | postgres:15 | 5432 | -- | PostgreSQL database shared by the STS and Auth Bridge services. Initialized with the required databases and roles on first startup. |
service-sts | portal/service-sts | 8092 | postgres | Security Token Service. Handles OAuth2 authorization server functionality, token issuance, and upstream federation with providers like SURF and Keycloak. |
service-auth-bridge | portal/service-auth-bridge | 8090 | postgres | Auth Bridge service. Manages identity matching, reconciliation sessions, identity link bindings, and the external API for third-party lookups. |
service-blobstore-camel | portal/blobstore | 8081 | -- | Blob storage service based on Apache Camel. Handles document and artifact storage. Does not depend on the database. |
portal | portal/frontend | 3000 | service-sts, service-auth-bridge | Next.js frontend application. Depends on both backend services being available for API calls. |
Startup Order
Docker Compose manages startup ordering through the depends_on configuration with health checks. The startup sequence is:
- PostgreSQL starts first and becomes healthy when it accepts connections.
- STS and Auth Bridge start in parallel once PostgreSQL is healthy. Each runs its own database migrations on startup.
- Blobstore starts independently (no database dependency).
- Portal starts last, once both STS and Auth Bridge report healthy status.
Environment Variables
Each service is configured through environment variables. The following sections list the key variables for each service, organized by category.
PostgreSQL
| Variable | Default | Description |
|---|---|---|
POSTGRES_USER | portal | Superuser name for the PostgreSQL instance. |
POSTGRES_PASSWORD | portal | Superuser password. Must be changed for production. |
POSTGRES_DB | portal | Default database name. |
STS (Service Token Service)
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | jdbc:postgresql://postgres:5432/sts | JDBC connection URL for the STS database. |
DATABASE_USERNAME | portal | Database username. |
DATABASE_PASSWORD | portal | Database password. |
ISSUER_URL | http://localhost:8092 | The issuer URL included in issued tokens. Must match the externally reachable URL in production. |
SURF_CLIENT_ID | -- | OIDC client ID registered with SURF for federation. |
SURF_CLIENT_SECRET | -- | OIDC client secret for SURF. |
SURF_DISCOVERY_URL | -- | OIDC discovery endpoint URL for SURF. |
KEYCLOAK_URL | http://keycloak:8080 | Keycloak instance URL for upstream authentication. |
KEYCLOAK_REALM | portal | Keycloak realm name. |
KEYCLOAK_CLIENT_ID | portal-sts | OIDC client ID registered in Keycloak. |
KEYCLOAK_CLIENT_SECRET | -- | OIDC client secret for Keycloak. |
Auth Bridge
| Variable | Default | Description |
|---|---|---|
DATABASE_URL | jdbc:postgresql://postgres:5432/auth_bridge | JDBC connection URL for the Auth Bridge database. |
DATABASE_USERNAME | portal | Database username. |
DATABASE_PASSWORD | portal | Database password. |
DATABASE_MAX_POOL_SIZE | 5 | Maximum number of connections in the connection pool. |
KMS_PROVIDER | software | KMS provider type. Use software for development, azure or aws for production. |
KMS_KEY_A_ID | -- | Key identifier for HMAC Key A (holder hashing). |
KMS_KEY_B_ID | -- | Key identifier for HMAC Key B (institution hashing). |
KMS_KEY_C_ID | -- | Key identifier for AES Key C (encryption). |
EXTERNAL_API_JWT_ISSUER | -- | Expected issuer claim in JWT tokens for the external API. |
EXTERNAL_API_JWT_AUDIENCE | -- | Expected audience claim in JWT tokens for the external API. |
RETENTION_INACTIVE_DAYS | 730 | Days of inactivity before soft deletion of bindings. |
RETENTION_SOFT_DELETE_DAYS | 30 | Days a soft-deleted record is retained before hard deletion. |
Portal (Frontend)
| Variable | Default | Description |
|---|---|---|
NEXTAUTH_URL | http://localhost:3000 | The canonical URL of the portal. Must match the externally reachable URL in production. |
NEXTAUTH_SECRET | -- | Secret used for encrypting NextAuth.js session tokens. Must be a strong random value in production. |
STS_URL | http://service-sts:8092 | Internal URL of the STS service. |
AUTH_BRIDGE_URL | http://service-auth-bridge:8090 | Internal URL of the Auth Bridge service. |
NEXT_PUBLIC_STS_URL | http://localhost:8092 | Publicly accessible URL of the STS service (used by the browser). |
Blobstore
| Variable | Default | Description |
|---|---|---|
BLOB_STORAGE_PATH | /data/blobs | File system path where blobs are stored inside the container. |
MAX_FILE_SIZE | 10MB | Maximum allowed file size for uploads. |
Volumes
The Docker Compose configuration defines the following persistent volumes:
| Volume | Mounted To | Service | Description |
|---|---|---|---|
pgdata | /var/lib/postgresql/data | postgres | PostgreSQL data directory. Persists database contents across container restarts. |
blobdata | /data/blobs | service-blobstore-camel | Blob storage directory. Persists uploaded files across container restarts. |
auth-bridge-keystore | /app/keystore | service-auth-bridge | Keystore directory for the Auth Bridge. In development mode with the software KMS provider, key material is stored here. Not used when an external KMS (Azure Key Vault, AWS KMS) is configured. |
To inspect the contents of a volume:
docker volume inspect deploy-docker_pgdata
Production Deployment Considerations
The Docker Compose setup is designed for local development and testing. Deploying to production requires several important changes:
External PostgreSQL
In production, use a managed PostgreSQL service (such as Azure Database for PostgreSQL or AWS RDS) rather than the containerized PostgreSQL instance. This provides:
- Automated backups and point-in-time recovery
- High availability with failover
- Monitoring and alerting
- Security patching
Update the DATABASE_URL environment variables for both STS and Auth Bridge to point to the external PostgreSQL instance.
Azure Key Vault for KMS
The software KMS provider stores key material on the local filesystem and is suitable only for development. In production, configure the Auth Bridge to use Azure Key Vault (or AWS KMS) for cryptographic key management:
sphereon:
app:
kms:
provider: azure
azure:
vault-url: "https://your-vault.vault.azure.net/"
tenant-id: "${env:AZURE_TENANT_ID}"
client-id: "${env:AZURE_CLIENT_ID}"
client-secret: "${env:AZURE_CLIENT_SECRET}"
This ensures that cryptographic keys are managed by a hardware security module (HSM) and never leave the KMS boundary.
Real SURF Credentials
The development setup may use mock or test credentials for SURF federation. In production, register proper OIDC client credentials with SURF and configure them in the STS environment variables.
HTTPS Termination
All services must be accessed over HTTPS in production. Use a reverse proxy (such as NGINX, Traefik, or a cloud load balancer) to terminate TLS and forward traffic to the backend services. Ensure that:
- The
ISSUER_URLfor the STS uses thehttps://scheme. - The
NEXTAUTH_URLfor the portal uses thehttps://scheme. - The
NEXT_PUBLIC_STS_URLuses thehttps://scheme. - All redirect URIs registered with OIDC providers use
https://.
NEXTAUTH_SECRET
The NEXTAUTH_SECRET must be set to a cryptographically strong random value in production. Generate one with:
openssl rand -base64 32
This secret is used to encrypt session tokens and must remain consistent across portal container restarts. Store it in a secrets manager rather than in plain text in the Docker Compose file.
Image Registry
In production, push built images to a private container registry (Azure Container Registry, AWS ECR, GitHub Container Registry) and reference them by digest rather than tag to ensure immutable deployments.
Resource Limits
Set CPU and memory limits on all containers to prevent any single service from consuming all available resources:
services:
service-auth-bridge:
deploy:
resources:
limits:
cpus: '2'
memory: 1G
reservations:
cpus: '0.5'
memory: 512M
Health Checks
Each service exposes a health check endpoint that can be used for monitoring and orchestration:
| Service | Health Endpoint | Healthy Response |
|---|---|---|
| Portal | GET /api/health | 200 OK with {"status": "ok"} |
| STS | GET /health | 200 OK |
| Auth Bridge | GET /health | 200 OK with readiness details |
| PostgreSQL | TCP connection on port 5432 | Connection accepted |
Docker Compose uses these health checks to determine when a service is ready to accept traffic and to manage the startup ordering of dependent services. In a Kubernetes deployment, these same endpoints should be configured as liveness and readiness probes.
Verifying Health After Startup
After starting the services, verify that all health checks pass:
# Check portal health
curl -s http://localhost:3000/api/health | jq .
# Check STS health
curl -s http://localhost:8092/health | jq .
# Check Auth Bridge health
curl -s http://localhost:8090/health | jq .
If any service reports unhealthy, check the container logs for error details:
docker compose logs service-auth-bridge --tail 100