PRODUCTION INFRASTRUCTURE

12 Containers.
One Docker Compose.

How I deploy and operate .NET applications in production. A complete containerized stack on AWS Lightsail Ubuntu 24 with SSL termination, load balancing, and automatic certificate renewal.

12
Containers
50%
Resource Usage
99.7%
Uptime
0
Manual Deploys

The 12-Container Stack

Every service runs in its own container, communicating via Docker's internal network. Only HAProxy exposes ports to the internet.

HAProxy 2.9

:443 → internal

🌐

ASP.NET API

:80 internal

💎

Blazor WASM

:80 internal

🗄️

SQL Server 2025

:1433 internal

🍃

MongoDB 7.0

:27017 internal

Redis 7

:6379 internal

🐰

RabbitMQ 3

:5672 internal

🔗

Neo4j 5.15

:7687 internal

🔍

Elasticsearch 8

:9200 internal

🔮

Oracle 23ai

:1521 internal

📊

Portainer

:9443 mgmt

🔒

Certbot

Let's Encrypt

Production-Grade Features

Not a dev environment. This is how real enterprise applications run.

🔐

SSL Termination with HAProxy

HAProxy handles all HTTPS on port 443, terminates SSL, then forwards plain HTTP to internal containers. .NET apps don't manage certificates.

bind *:443 ssl crt /etc/haproxy/certs/
mode http → backend servers on :80
🚀

HTTP/2 for Clients

Clients get HTTP/2 for multiplexed requests and header compression. Backend stays HTTP/1.1 because Docker's internal network doesn't need the overhead.

alpn h2,http/1.1
Client ↔ HAProxy: HTTP/2
HAProxy ↔ Backend: HTTP/1.1
🔌

SignalR WebSocket Routing

WebSockets need special handling: sticky sessions, extended timeouts (1 hour vs 30 seconds), and proper upgrade header forwarding.

acl is_signalr path_beg /hub
timeout tunnel 1h
balance source (sticky)
📁

GridFS Large File Handling

MongoDB GridFS stores large files. Uploads can take minutes. HAProxy has a separate backend with 5-minute timeouts for /gridfs/* paths.

timeout server 300s
timeout client 300s
acl is_gridfs path_beg /gridfs
🌐

Container Networking

All services use Docker's internal bridge network with container names as hostnames. The API connects to 'sqlserver:1433', not IP addresses.

networks:
app-network:
driver: bridge
DNS resolution automatic
🔄

Auto Certificate Renewal

Cron job runs daily at 3 AM. If certificates are within 30 days of expiry, Certbot renews them. Zero manual intervention ever.

0 3 * * * /opt/renew-certs.sh
certbot renew --standalone
cat cert.pem key.pem > haproxy.pem

Traffic Flow

How a request travels from the internet to your .NET application.

🌍 Internet

HTTPS :443

⚡ HAProxy

SSL termination

🔀 Route

/api vs /hub vs /*

🌐 Backend

HTTP :80

🗄️ Data

SQL/Mongo/Redis

Docker Compose Structure

The actual docker-compose.yml that runs everything. One file, 12 services.

docker-compose.yml
# Production stack - AWS Lightsail Ubuntu 24 version: '3.8' services: haproxy: image: haproxy:2.9-alpine ports: - "443:443" - "80:80" volumes: - ./haproxy:/usr/local/etc/haproxy:ro - ./certs:/etc/haproxy/certs:ro networks: [app-network] restart: always api: image: pcishield-api:latest environment: - ASPNETCORE_URLS=http://+:80 - ConnectionStrings__Default=Server=sqlserver;... networks: [app-network] depends_on: [sqlserver, redis, rabbitmq] sqlserver: image: mcr.microsoft.com/mssql/server:2025-latest environment: - ACCEPT_EULA=Y - MSSQL_SA_PASSWORD=[secure] volumes: - sqlserver-data:/var/opt/mssql networks: [app-network] mongodb: image: mongo:7.0 volumes: - mongodb-data:/data/db networks: [app-network] redis: image: redis:7-alpine networks: [app-network] rabbitmq: image: rabbitmq:3-management-alpine networks: [app-network] # ... neo4j, elasticsearch, oracle, portainer ... networks: app-network: driver: bridge volumes: sqlserver-data: mongodb-data:

Technology Stack

Production-proven infrastructure components.

Docker Compose
HAProxy 2.9
Let's Encrypt
Ubuntu 24.04
AWS Lightsail
Portainer
SQL Server 2025
MongoDB 7.0
Redis 7
RabbitMQ 3