Docker Compose Deployment
Deploy RecordPlatform using Docker Compose.
Prerequisites
- Docker 20.10+
- Docker Compose 2.0+
- 8GB RAM minimum
Infrastructure Services
Create docker-compose.infra.yml:
services:
nacos:
image: nacos/nacos-server:v2.3.0
container_name: nacos
ports:
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${NACOS_PORT:-8848}:8848"
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${NACOS_GRPC_PORT:-9848}:9848"
environment:
- MODE=standalone
- NACOS_AUTH_ENABLE=true
- NACOS_AUTH_TOKEN=${NACOS_AUTH_TOKEN:?Set NACOS_AUTH_TOKEN in .env}
- NACOS_AUTH_IDENTITY_KEY=${NACOS_AUTH_IDENTITY_KEY:?Set NACOS_AUTH_IDENTITY_KEY in .env}
- NACOS_AUTH_IDENTITY_VALUE=${NACOS_AUTH_IDENTITY_VALUE:?Set NACOS_AUTH_IDENTITY_VALUE in .env}
volumes:
- nacos_data:/home/nacos/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8848/nacos/v1/console/health/readiness"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
mysql:
image: mysql:8.0
container_name: mysql
ports:
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${DB_PORT:-3306}:3306"
environment:
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD:?Set DB_PASSWORD in .env}
- MYSQL_DATABASE=RecordPlatform
volumes:
- mysql_data:/var/lib/mysql
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${DB_PASSWORD:?Set DB_PASSWORD in .env}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: redis
ports:
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${REDIS_PORT:-6379}:6379"
command: redis-server --requirepass ${REDIS_PASSWORD:?Set REDIS_PASSWORD in .env}
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:?Set REDIS_PASSWORD in .env}", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 5s
restart: unless-stopped
rabbitmq:
image: rabbitmq:3-management
container_name: rabbitmq
ports:
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${RABBITMQ_PORT:-5672}:5672"
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${RABBITMQ_MANAGEMENT_PORT:-15672}:15672"
environment:
- RABBITMQ_DEFAULT_USER=${RABBITMQ_USERNAME:?Set RABBITMQ_USERNAME in .env}
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASSWORD:?Set RABBITMQ_PASSWORD in .env}
volumes:
- rabbitmq_data:/var/lib/rabbitmq
healthcheck:
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
minio-a:
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
container_name: minio-a
ports:
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${MINIO_A_API_PORT:-9000}:9000"
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${MINIO_A_CONSOLE_PORT:-9001}:9001"
environment:
- MINIO_ROOT_USER=${S3_ACCESS_KEY:?Set S3_ACCESS_KEY in .env}
- MINIO_ROOT_PASSWORD=${S3_SECRET_KEY:?Set S3_SECRET_KEY in .env}
command: server /data --console-address ":9001"
volumes:
- minio_a_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
minio-b:
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
container_name: minio-b
ports:
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${MINIO_B_API_PORT:-9010}:9000"
- "${INFRA_BIND_ADDRESS:-127.0.0.1}:${MINIO_B_CONSOLE_PORT:-9011}:9001"
environment:
- MINIO_ROOT_USER=${S3_ACCESS_KEY:?Set S3_ACCESS_KEY in .env}
- MINIO_ROOT_PASSWORD=${S3_SECRET_KEY:?Set S3_SECRET_KEY in .env}
command: server /data --console-address ":9001"
volumes:
- minio_b_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
# ── Observability (OpenTelemetry) ──────────────────────────────────────────
jaeger:
image: jaegertracing/all-in-one:1.68
container_name: jaeger
ports:
- "${OBSERVABILITY_BIND_ADDRESS:-127.0.0.1}:${JAEGER_UI_PORT:-16686}:16686" # Jaeger UI
expose:
- "4317" # OTLP gRPC (internal, used by otel-collector)
- "4318" # OTLP HTTP
- "14269" # Admin / health
environment:
- COLLECTOR_OTLP_ENABLED=true
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:14269/ || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: unless-stopped
otel-collector:
image: otel/opentelemetry-collector-contrib:0.123.0
container_name: otel-collector
ports:
- "${OBSERVABILITY_BIND_ADDRESS:-127.0.0.1}:${OTEL_GRPC_PORT:-4317}:4317" # OTLP gRPC
- "${OBSERVABILITY_BIND_ADDRESS:-127.0.0.1}:${OTEL_HTTP_PORT:-4318}:4318" # OTLP HTTP
- "${OBSERVABILITY_BIND_ADDRESS:-127.0.0.1}:${OTEL_PROMETHEUS_PORT:-8889}:8889" # Prometheus metrics
volumes:
- ./config/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro
depends_on:
jaeger:
condition: service_healthy
restart: unless-stopped
volumes:
nacos_data:
mysql_data:
redis_data:
rabbitmq_data:
minio_a_data:
minio_b_data:Application Services
Create docker-compose.app.yml:
CRITICAL: DUBBO_HOST Configuration
In Docker environments, Dubbo provider services (storage, fisco) must register with host-accessible IPs rather than internal Docker bridge IPs. Without proper DUBBO_HOST configuration, the backend cannot discover and call provider services, resulting in "No provider available" errors.
services:
storage:
build:
context: .
dockerfile: platform-storage/Dockerfile
container_name: platform-storage
ports:
- "8092:8092"
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- DUBBO_HOST=${DUBBO_HOST:-host.docker.internal} # Required for Docker
depends_on:
- nacos
- redis
restart: unless-stopped
fisco:
build:
context: .
dockerfile: platform-fisco/Dockerfile
container_name: platform-fisco
ports:
- "8091:8091"
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
- NACOS_PORT=${NACOS_PORT:-8848}
- NACOS_USERNAME=${NACOS_USERNAME:?Set NACOS_USERNAME in .env}
- NACOS_PASSWORD=${NACOS_PASSWORD:?Set NACOS_PASSWORD in .env}
- DUBBO_HOST=${DUBBO_HOST:-host.docker.internal} # Required for Docker
- BLOCKCHAIN_RPC_TOKEN=${BLOCKCHAIN_RPC_TOKEN:?Set BLOCKCHAIN_RPC_TOKEN in .env}
- BLOCKCHAIN_ACTIVE=${BLOCKCHAIN_ACTIVE:-local-fisco}
- BSN_BESU_RPC_URL=${BSN_BESU_RPC_URL:-}
- BSN_BESU_CHAIN_ID=${BSN_BESU_CHAIN_ID:-}
- BSN_BESU_PRIVATE_KEY=${BSN_BESU_PRIVATE_KEY:-} # Use a deployment secret in production
- BSN_BESU_CONTRACT_STORAGE=${BSN_BESU_CONTRACT_STORAGE:-}
- BSN_BESU_CONTRACT_SHARING=${BSN_BESU_CONTRACT_SHARING:-}
- BSN_BESU_NONCE_STATE_DIRECTORY=/var/lib/record-platform/besu-nonce
# Required deployment receipt evidence for the selected active chain.
- FISCO_SHARING_DEPLOYMENT_TX=${FISCO_SHARING_DEPLOYMENT_TX:?Set FISCO_SHARING_DEPLOYMENT_TX in .env}
- FISCO_SHARING_DEPLOYMENT_BLOCK=${FISCO_SHARING_DEPLOYMENT_BLOCK:?Set FISCO_SHARING_DEPLOYMENT_BLOCK in .env}
- FISCO_SHARING_DEPLOYMENT_EFFECTIVE_AT=${FISCO_SHARING_DEPLOYMENT_EFFECTIVE_AT:?Set FISCO_SHARING_DEPLOYMENT_EFFECTIVE_AT in .env}
- FISCO_STORAGE_DEPLOYMENT_TX=${FISCO_STORAGE_DEPLOYMENT_TX:?Set FISCO_STORAGE_DEPLOYMENT_TX in .env}
- FISCO_STORAGE_DEPLOYMENT_BLOCK=${FISCO_STORAGE_DEPLOYMENT_BLOCK:?Set FISCO_STORAGE_DEPLOYMENT_BLOCK in .env}
- FISCO_STORAGE_DEPLOYMENT_EFFECTIVE_AT=${FISCO_STORAGE_DEPLOYMENT_EFFECTIVE_AT:?Set FISCO_STORAGE_DEPLOYMENT_EFFECTIVE_AT in .env}
volumes:
- ./platform-fisco/src/main/resources/conf:/app/conf
# Required when BLOCKCHAIN_ACTIVE=bsn-besu; the image prepares this path for user app.
- besu_nonce_data:/var/lib/record-platform/besu-nonce
depends_on:
- nacos
restart: unless-stopped
backend:
build:
context: .
dockerfile: platform-backend/Dockerfile
container_name: platform-backend
ports:
- "8000:8000"
environment:
- SPRING_PROFILES_ACTIVE=prod
- NACOS_HOST=nacos
# Backend is Dubbo consumer, doesn't need DUBBO_HOST
depends_on:
- storage
- fisco
- mysql
- redis
- rabbitmq
restart: unless-stopped
frontend:
build:
context: ./platform-frontend
dockerfile: Dockerfile
container_name: platform-frontend
ports:
- "80:80"
environment:
- PUBLIC_API_BASE_URL=http://backend:8000/record-platform
depends_on:
- backend
restart: unless-stopped
volumes:
# Local named volume: durable across container replacement on this Docker host.
besu_nonce_data:Deployment Steps
1. Configure Environment
cp .env.example .env
vim .env2. Start Infrastructure
docker-compose -f docker-compose.infra.yml up -d3. Wait for Services
# Check Nacos
curl http://localhost:8848/nacos
# Check MySQL
docker-compose -f docker-compose.infra.yml exec mysql mysql -uroot -p -e "SELECT 1"4. Build and Start Applications
# Build images
docker-compose -f docker-compose.app.yml build
# Start services
docker-compose -f docker-compose.app.yml up -d5. Verify Deployment
# Check logs
docker-compose -f docker-compose.app.yml logs -f
# Health check
curl http://localhost:8000/record-platform/actuator/healthScaling
For high availability, scale backend instances:
docker-compose -f docker-compose.app.yml up -d --scale backend=3Use a load balancer (nginx, traefik) in front of backend instances.
Do not scale the fisco service when BLOCKCHAIN_ACTIVE=bsn-besu instances share one signer. The fixed container name is only an additional Compose guard; the authoritative JVM startup gate is the exclusive (chainId, signer) file lock in BSN_BESU_NONCE_STATE_DIRECTORY. The example uses an image-prepared named volume so the non-root app user can write it; keep that volume durable and attach it to only the fenced replacement on the same Docker host. If an operator replaces it with a bind/shared mount, that mount must be pre-provisioned writable by the image's app user and verified for atomic-move, directory-fsync, and file-lock semantics before activation. Independent host directories and ordinary remote filesystems with unverified lock semantics do not provide cross-host fencing; same-signer active-active therefore remains unsupported.
DUBBO_HOST Configuration
The DUBBO_HOST environment variable is critical for Docker deployments. It specifies the IP address that Dubbo providers register with Nacos for service discovery.
Configuration by Scenario
| Scenario | DUBBO_HOST Value | Notes |
|---|---|---|
| Docker Desktop (Mac/Windows) | host.docker.internal | Special DNS name resolving to host |
| Docker on Linux | Host machine IP (e.g., 192.168.1.100) | Use actual network IP |
| Docker Compose (same network) | Container name (e.g., platform-storage) | Internal Docker DNS |
| Kubernetes | Pod IP or Service IP | Use K8s service discovery |
Troubleshooting
Symptom: Backend starts but cannot call storage/fisco services
Check service registration:
# View registered services in Nacos
curl "http://localhost:8848/nacos/v1/ns/instance/list?serviceName=platform-storage"If the registered IP shows 172.x.x.x (Docker internal IP), the DUBBO_HOST is not configured correctly.
Fix: Add DUBBO_HOST to your .env file:
# .env
DUBBO_HOST=host.docker.internal # For Docker Desktop
# or
DUBBO_HOST=192.168.1.100 # For Linux, use actual host IP