Skip to main content

Docker Deployment

AuthProxy uses a base image + binary mounting strategy for flexible, multi-tenant deployment. This approach enables easy versioning, rollback, and efficient storage.

Deployment Philosophy

DO NOT store container versions. Instead:

  • Store binaries by semantic version
  • Use single base image for all services
  • Mount binaries at runtime via volumes

Benefits

Easy Rollback - Just mount previous version ✅ Storage Efficient - One base image serves many projects ✅ Fast Deployment - No image build needed ✅ Version Flexibility - Each module independently versioned ✅ Multi-Tenant Ready - Same base image, different binaries per customer

Base Image

AuthProxy uses a minimal .NET 10 Alpine base image:

# itbuild/dotnet-base:10.0
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine

WORKDIR /app

# Health check
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:80/health || exit 1

ENTRYPOINT ["dotnet"]

Why Alpine?

  • Minimal size (~100MB vs ~200MB for Debian)
  • Reduced attack surface
  • Faster container startup

Multi-Tenant Deployment

Each customer project has:

  • Unique port (e.g., 8001-8999 for DEV)
  • Separate VM or shared infrastructure
  • Separate databases per module
  • Isolated Docker network

File Structure

/projects/{projectId}/
├── configs/
│ ├── authproxy.json # AuthProxy configuration
│ ├── wallet.json # TrexWallet configuration
│ └── core.json # Core module configuration
├── AuthProxy/
│ ├── 1.0.0/ # Binaries + wwwroot
│ │ ├── AuthProxy.dll
│ │ ├── *.dll
│ │ └── wwwroot/ # PWA frontends
│ ├── 1.1.0/
│ └── 1.2.0/ # Latest version
├── TrexWallet/
│ ├── 1.0.0/
│ └── 1.1.0/
└── Core/
└── 1.0.0/

Deployment Examples

AuthProxy (Public-Facing)

AuthProxy needs host port mapping since it serves PWA frontends and is the entry point:

docker run -d \
--name project123-authproxy \
-p 8001:80 \
-v /projects/123/AuthProxy/1.2.0:/app:ro \
-v /projects/123/configs/authproxy.json:/app/appsettings.json:ro \
-e ASPNETCORE_ENVIRONMENT=Production \
--network project123_net \
itbuild/dotnet-base:10.0 \
AuthProxy.dll

Parameters Explained:

ParameterPurposeValue
--nameContainer nameproject{id}-authproxy
-p 8001:80Host port mappingDEV: 8001-8999, PROD: 80/443
-v .../1.2.0:/app:roMount binaries (read-only)Specific version
-v .../authproxy.json:/app/appsettings.json:roMount configCustomer-specific config
--networkDocker networkIsolated per project
AuthProxy.dllEntry pointPassed to dotnet

TrexWallet (Internal Service)

Internal services don't need host ports - they use Docker network:

docker run -d \
--name project123-wallet \
--network project123_net \
-v /projects/123/TrexWallet/1.0.0:/app:ro \
-v /projects/123/configs/wallet.json:/app/appsettings.json:ro \
-e ASPNETCORE_ENVIRONMENT=Production \
itbuild/dotnet-base:10.0 \
TrexWallet.dll

Key Differences:

  • ❌ NO -p flag (not exposed to host)
  • ✅ Only on Docker network
  • ✅ Accessed via http://project123-wallet:80

Core Module (Customer's Business Logic)

docker run -d \
--name project123-core \
--network project123_net \
-v /projects/123/Core/1.0.0:/app:ro \
-v /projects/123/configs/core.json:/app/appsettings.json:ro \
-e ASPNETCORE_ENVIRONMENT=Production \
itbuild/dotnet-base:10.0 \
Core.dll

Port Management

Shared Development Environment

  • Port Range: 8001-8999 (per-customer projects on shared dev stand)
  • Assignment: Automatic from database
  • Edge forwarding: public customer port → project VM customer port

The assigned port is shown in the project administration UI and deployment logs.

DNS Configuration:

customer-domain:customer-port → edge gateway → project VM:customer-port

Production Environment

Option A: Direct Port 80/443

AuthProxy listens on the standard HTTP/HTTPS ports.

Option B: Behind Reverse Proxy

A reverse proxy terminates TLS and forwards traffic to AuthProxy on an internal container port.

AuthProxy Port Mapping

Host Mode (direct port exposure):

-p 8001:80  # Host 8001 → Container 80

Why Host Mode for AuthProxy?

  • AuthProxy IS the reverse proxy
  • Direct client access needed
  • Serves static files (PWA frontends)

Internal Services

Network Mode (no host ports):

--network project123_net  # No -p flag

Access:

http://project123-wallet:80/wallet/v1/balance
http://project123-chat:80/chat/v1/messages
http://project123-core:80/core/v1/api

Docker Compose

Complete Multi-Tenant Stack

version: '3.8'

services:
authproxy:
image: itbuild/dotnet-base:10.0
container_name: project123-authproxy
ports:
- "8001:80"
volumes:
- /projects/123/AuthProxy/1.2.0:/app:ro
- /projects/123/configs/authproxy.json:/app/appsettings.json:ro
environment:
- ASPNETCORE_ENVIRONMENT=Production
command: ["AuthProxy.dll"]
networks:
- project123_net
depends_on:
- sql-server
restart: unless-stopped

wallet:
image: itbuild/dotnet-base:10.0
container_name: project123-wallet
volumes:
- /projects/123/TrexWallet/1.0.0:/app:ro
- /projects/123/configs/wallet.json:/app/appsettings.json:ro
environment:
- ASPNETCORE_ENVIRONMENT=Production
command: ["TrexWallet.dll"]
networks:
- project123_net
depends_on:
- sql-server
restart: unless-stopped

core:
image: itbuild/dotnet-base:10.0
container_name: project123-core
volumes:
- /projects/123/Core/1.0.0:/app:ro
- /projects/123/configs/core.json:/app/appsettings.json:ro
environment:
- ASPNETCORE_ENVIRONMENT=Production
command: ["Core.dll"]
networks:
- project123_net
depends_on:
- sql-server
restart: unless-stopped

sql-server:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: project123-sql
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=SqlServerPass123!
volumes:
- sql_data:/var/opt/mssql
networks:
- project123_net
restart: unless-stopped

networks:
project123_net:
driver: bridge

volumes:
sql_data:

Versioning & Rollback

Semantic Versioning

Binaries stored by semantic version:

/projects/123/AuthProxy/
├── 1.0.0/
├── 1.1.0/
├── 1.2.0/ (current - stable)
├── 1.2.1/ (failed deployment)
└── 1.3.0/ (testing)

Rollback Procedure

1. Stop Current Container:

docker stop project123-authproxy
docker rm project123-authproxy

2. Deploy Previous Version:

docker run -d \
--name project123-authproxy \
-p 8001:80 \
-v /projects/123/AuthProxy/1.2.0:/app:ro \ # ← Previous version
-v /projects/123/configs/authproxy.json:/app/appsettings.json:ro \
--network project123_net \
itbuild/dotnet-base:10.0 \
AuthProxy.dll

3. Verify:

curl http://localhost:8001/health

Automatic Rollback Script

#!/bin/bash
# rollback.sh

PROJECT_ID=$1
SERVICE=$2
VERSION=$3

CONTAINER_NAME="project${PROJECT_ID}-${SERVICE}"
BINARY_PATH="/projects/${PROJECT_ID}/${SERVICE}/${VERSION}"
CONFIG_PATH="/projects/${PROJECT_ID}/configs/${SERVICE,,}.json"

# Stop and remove
docker stop ${CONTAINER_NAME}
docker rm ${CONTAINER_NAME}

# Redeploy
if [ "$SERVICE" = "AuthProxy" ]; then
# AuthProxy needs port
docker run -d \
--name ${CONTAINER_NAME} \
-p 8001:80 \
-v ${BINARY_PATH}:/app:ro \
-v ${CONFIG_PATH}:/app/appsettings.json:ro \
--network project${PROJECT_ID}_net \
itbuild/dotnet-base:10.0 \
${SERVICE}.dll
else
# Internal service
docker run -d \
--name ${CONTAINER_NAME} \
--network project${PROJECT_ID}_net \
-v ${BINARY_PATH}:/app:ro \
-v ${CONFIG_PATH}:/app/appsettings.json:ro \
itbuild/dotnet-base:10.0 \
${SERVICE}.dll
fi

echo "Rolled back ${SERVICE} to version ${VERSION}"

Usage:

./rollback.sh 123 AuthProxy 1.2.0
./rollback.sh 123 TrexWallet 1.0.0

Configuration

appsettings.json (Mounted)

Configuration is outside the container:

{
"ConnectionStrings": {
"DefaultConnection": "Server=project123-sql;Database=project123_authproxy;User Id=authproxy;Password=SecurePass123;Encrypt=true;"
},
"Config": {
"ServiceId": 10,
"Project": "CustomerProject",
"Domain": "app.customer.com",
"CoreApiKey": "generated_secure_key",
"Modules": {
"Wallet": "http://project123-wallet:80",
"Core": "http://project123-core:80"
}
},
"FIDO2": {
"serverDomain": "customer.com",
"serverName": "app.customer.com",
"origins": ["https://app.customer.com"]
},
"Uptrace": {
"Dsn": "http://uptrace-server:14318",
"ServiceName": "authproxy"
}
}

Why Mount Config?

  • Update config without rebuilding
  • Customer-specific settings
  • Secrets management (can be encrypted)

Environment Variables

Override config via environment:

docker run -d \
-e ConnectionStrings__DefaultConnection="..." \
-e ServiceId="10" \
-e Config__Domain="app.customer.com" \
...

Secrets Management

Option 1: Docker Secrets

services:
authproxy:
secrets:
- db_password
- api_key

secrets:
db_password:
external: true
api_key:
external: true

Option 2: Encrypted in Config

Sensitive values can be encrypted in configuration:

{
"Config": {
"CoreApiKey": "encrypted:AES256:base64_encrypted_value"
}
}

Networking

Docker Network Per Project

Each project has isolated network:

docker network create project123_net

Services on Network:

  • project123-authproxy (8001:80)
  • project123-wallet (80 internal)
  • project123-core (80 internal)
  • project123-sql (1433 internal)

Inter-Service Communication:

AuthProxy → http://project123-wallet:80/wallet/v1/balance
Wallet → http://project123-core:80/core/v1/users/123

External Access

DEV:

Internet → customer endpoint → edge gateway → Docker:80

PROD:

Internet → app.customer.com → Nginx/Caddy → Docker:8080

Monitoring & Health

Health Checks

All services expose /health endpoint:

curl http://localhost:8001/health
# Response: {"status": "healthy"}

Docker Health Check:

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:80/health || exit 1

Uptrace Integration

All containers send traces/logs to Uptrace:

services:
uptrace:
image: uptrace/uptrace:latest
ports:
- "14317:14317" # gRPC
- "14318:14318" # HTTP
volumes:
- uptrace_data:/var/lib/uptrace

volumes:
uptrace_data:

Container Logs

# View logs
docker logs -f project123-authproxy

# Last 100 lines
docker logs --tail 100 project123-authproxy

# Since timestamp
docker logs --since 2024-01-01T00:00:00 project123-authproxy

Production Considerations

Resource Limits

services:
authproxy:
deploy:
resources:
limits:
cpus: '2.0'
memory: 2G
reservations:
cpus: '1.0'
memory: 1G

Restart Policies

restart: unless-stopped  # Recommended for production

SSL/TLS

Option A: Terminate at Nginx

server {
listen 443 ssl http2;
server_name app.customer.com;

ssl_certificate /etc/ssl/certs/customer.crt;
ssl_certificate_key /etc/ssl/private/customer.key;

location / {
proxy_pass http://localhost:8001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

Option B: Kestrel HTTPS

docker run -d \
-p 443:443 \
-v /certs/customer.pfx:/app/certificate.pfx:ro \
-e ASPNETCORE_Kestrel__Certificates__Default__Path=/app/certificate.pfx \
-e ASPNETCORE_Kestrel__Certificates__Default__Password=CertPassword \
...

Backup & Disaster Recovery

Backup:

# Backup binaries
tar -czf authproxy-1.2.0.tar.gz /projects/123/AuthProxy/1.2.0

# Backup config
cp /projects/123/configs/*.json /backups/configs/

# Backup database
docker exec project123-sql /opt/mssql-tools/bin/sqlcmd \
-S localhost -U sa -P 'Password' \
-Q "BACKUP DATABASE project123_authproxy TO DISK='/var/opt/mssql/backup/authproxy.bak'"

Troubleshooting

Container Won't Start

Check logs:

docker logs project123-authproxy

Common issues:

  1. Port already in use: Change port or stop conflicting service
  2. Volume mount failed: Check path exists and has permissions
  3. Config error: Validate JSON syntax
  4. Database unreachable: Check network and connection string

Service Unreachable

Test from host:

curl http://localhost:8001/health

Test from another container:

docker exec -it project123-wallet wget -O- http://project123-authproxy:80/health

High Memory Usage

Check stats:

docker stats project123-authproxy

Set limits:

docker update --memory 2G project123-authproxy

Rollback Failed

Nuclear option (restore from backup):

# Stop all
docker-compose down

# Restore binaries
tar -xzf authproxy-1.2.0.tar.gz -C /

# Restore config
cp /backups/configs/*.json /projects/123/configs/

# Start
docker-compose up -d