Configuration & Environment
Quản lý biến môi trường, cấu hình Nginx reverse proxy và connection strings
File .env
Docker Compose tự động đọc file .env cùng thư mục với docker-compose.yml. Đây là nơi
tập trung mọi giá trị cấu hình:
# ── PostgreSQL ── POSTGRES_DB=myapp_db POSTGRES_USER=myapp_user POSTGRES_PASSWORD=S3cur3P@ssw0rd!Change-This # ── Redis ── REDIS_PASSWORD=R3d1sP@ss!Change-This # ── Connection Strings (cho ASP.NET) ── DB_CONNECTION=Host=postgres;Port=5432;Database=myapp_db;Username=myapp_user;Password=S3cur3P@ssw0rd!Change-This REDIS_CONNECTION=redis:6379,password=R3d1sP@ss!Change-This # ── ASP.NET ── ASPNETCORE_ENVIRONMENT=Production JWT_SECRET=your-jwt-secret-key-at-least-32-chars JWT_ISSUER=https://app.example.com # ── Domain ── DOMAIN=app.example.com
.env chứa mọi secrets của hệ thống. Tuyệt đối không commit lên Git. Thêm
.env vào .gitignore và tạo file .env.example (không chứa giá trị thật) làm
template.
File .env.example để commit lên git làm hướng dẫn:
# Copy file này thành .env và điền giá trị thật # cp .env.example .env POSTGRES_DB=myapp_db POSTGRES_USER=myapp_user POSTGRES_PASSWORD=# Đặt password mạnh ở đây REDIS_PASSWORD=# Đặt password mạnh ở đây DB_CONNECTION=# Host=postgres;Port=5432;Database=...;Username=...;Password=... REDIS_CONNECTION=# redis:6379,password=... ASPNETCORE_ENVIRONMENT=Production JWT_SECRET=# Ít nhất 32 ký tự JWT_ISSUER=# https://your-domain.com DOMAIN=# your-domain.com
Chuỗi override cấu hình ASP.NET
ASP.NET Core có hệ thống configuration phân tầng. Giá trị ở tầng sau sẽ ghi đè tầng trước:
appsettings.json với giá trị mặc định (cho development), và override bằng env vars trong
docker-compose.yml cho production. Không cần sửa file JSON.
Ví dụ mapping giữa JSON config và environment variable:
// appsettings.json — giá trị mặc định cho development { "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Database=myapp_dev" }, "Redis": { "ConnectionString": "localhost:6379" }, "Jwt": { "Secret": "dev-secret-not-for-production", "Issuer": "https://localhost:5001" } }
# docker-compose.yml — override bằng env vars cho production backend: environment: # Dấu __ (double underscore) thay cho : trong JSON hierarchy - ConnectionStrings__DefaultConnection=${DB_CONNECTION} - Redis__ConnectionString=${REDIS_CONNECTION} - Jwt__Secret=${JWT_SECRET} - Jwt__Issuer=${JWT_ISSUER}
__ (double underscore) để phân cấp hierarchy trong env vars. Ví dụ:
ConnectionStrings__DefaultConnection tương đương ConnectionStrings:DefaultConnection
trong JSON.
Nginx Reverse Proxy
Nginx đóng vai trò entry point duy nhất, phân phối traffic đến frontend (React) hoặc backend (API) dựa trên URL path:
# nginx/nginx.conf events { worker_connections 1024; } http { # ── Upstream definitions ── upstream frontend { server frontend:80; } upstream backend { server backend:8080; } # ── Redirect HTTP → HTTPS ── server { listen 80; server_name app.example.com; return 301 https://$host$request_uri; } # ── Main HTTPS server ── server { listen 443 ssl; server_name app.example.com; # SSL certificates ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; # SSL best practices ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; # ── API requests → Backend ── location /api/ { proxy_pass http://backend; 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; # Timeout cho long-running requests proxy_read_timeout 60s; proxy_send_timeout 60s; } # ── Everything else → Frontend ── location / { proxy_pass http://frontend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # ── Gzip compression ── gzip on; gzip_types text/plain application/json application/javascript text/css; gzip_min_length 1000; } }
frontend, backend) thay vì IP. Docker DNS tự động resolve service name thành IP của
container.
Quản lý Secrets
So sánh các phương pháp quản lý secrets phổ biến:
.env file
- Dễ setup, Docker Compose hỗ trợ sẵn
- Phù hợp cho single server
- Cần quản lý permission file cẩn thận
- Không phù hợp cho team lớn
Docker Secrets
- Chỉ hỗ trợ Docker Swarm mode
- Secrets lưu encrypted trên disk
- Mount vào container dưới dạng file
- App cần đọc từ file thay vì env var
.env là lựa chọn thực tế nhất. Đảm bảo: (1)
file permission là 600 (chmod 600 .env), (2) nằm trong .gitignore, (3)
không bao giờ log ra giá trị secrets.
Biến môi trường theo service
Chi tiết env vars quan trọng của từng service:
POSTGRES_DB — Tên database sẽ được tạo tự động khi container khởi chạy lần đầu.
POSTGRES_USER — Username cho database. Tránh dùng postgres (superuser mặc
định).
POSTGRES_PASSWORD — Password cho user trên. Dùng password mạnh (ít nhất 16 ký tự, mix
chữ + số + ký tự đặc biệt).
PGDATA — (Optional) Thay đổi data directory bên trong container. Mặc định:
/var/lib/postgresql/data.
Redis không dùng env vars mà cấu hình qua command trong docker-compose:
--requirepass — Bật authentication. Bắt buộc cho production, dù
container không expose port ra ngoài.
--maxmemory 256mb — Giới hạn RAM sử dụng. Quan trọng vì Redis mặc định dùng unlimited
memory.
--maxmemory-policy allkeys-lru — Khi hết memory, tự xóa keys ít sử dụng nhất.
ASPNETCORE_ENVIRONMENT — Phải là Production. Ảnh hưởng đến logging level,
error pages, và config file nào được load.
ASPNETCORE_URLS — (Optional) URL Kestrel listen. Mặc định .NET 8 là
http://+:8080.
ConnectionStrings__DefaultConnection — Npgsql connection string. Lưu ý dùng
__ thay cho :.
Redis__ConnectionString — StackExchange.Redis connection string, format:
host:port,password=xxx.
React app được build thành static files nên không đọc env vars ở runtime.
Env vars được embed vào code lúc npm run build (chỉ các biến có prefix
REACT_APP_).
REACT_APP_API_URL — URL của API. Với reverse proxy, thường là /api
(relative path).
Nếu cần thay đổi config sau build, tạo file config.js ở /usr/share/nginx/html/
và load runtime.
Cấu hình Forwarded Headers
Khi ASP.NET chạy sau Nginx reverse proxy, cần cấu hình để nhận đúng IP và scheme (HTTPS) từ client:
// Program.cs var builder = WebApplication.CreateBuilder(args); // Cấu hình Forwarded Headers builder.Services.Configure<ForwardedHeadersOptions>(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; // Trust Nginx proxy (trong Docker network) options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); var app = builder.Build(); // PHẢI đặt TRƯỚC các middleware khác app.UseForwardedHeaders(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();
CORS Configuration
Với reverse proxy, frontend và API cùng domain nên thường không cần CORS. Nhưng nếu cần:
// Program.cs — Chỉ cần nếu frontend ở domain khác builder.Services.AddCors(options => { options.AddPolicy("Production", policy => { policy.WithOrigins("https://app.example.com") .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); // Sử dụng app.UseCors("Production");
/api/... (cùng origin) nên không cần CORS.
CORS chỉ cần thiết khi frontend và API ở khác domain hoặc port.