Docker & Compose Setup
Cài đặt Docker, viết Dockerfile và cấu hình docker-compose.yml cho toàn bộ stack
Cài đặt Docker Engine
Trên Ubuntu server, cài Docker Engine từ repository chính thức của Docker (không dùng bản từ apt mặc
định vì thường cũ hơn):
# 1. Cập nhật packages và cài dependencies sudo apt update sudo apt install -y ca-certificates curl gnupg # 2. Thêm Docker GPG key sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # 3. Thêm Docker repository echo \ "deb [arch=$(dpkg --print-architecture) \ signed-by=/etc/apt/keyrings/docker.gpg] \ https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 4. Cài Docker Engine + Compose plugin sudo apt update sudo apt install -y docker-ce docker-ce-cli \ containerd.io docker-buildx-plugin docker-compose-plugin # 5. Cho phép user hiện tại chạy docker không cần sudo sudo usermod -aG docker $USER newgrp docker # 6. Kiểm tra cài đặt docker --version docker compose version
docker compose (không có dấu
gạch ngang), thay thế cho docker-compose (V1) đã deprecated.
Cấu trúc thư mục dự án
Tổ chức thư mục rõ ràng giúp quản lý Dockerfile và config cho từng service:
my-app/ ├── docker-compose.yml # Orchestration file chính ├── docker-compose.override.yml # Override cho development (optional) ├── .env # Biến môi trường (KHÔNG commit lên git) ├── .dockerignore # Files bỏ qua khi build │ ├── frontend/ # React application │ ├── Dockerfile │ ├── nginx.conf # Nginx config cho React SPA │ ├── src/ │ ├── public/ │ └── package.json │ ├── backend/ # ASP.NET API │ ├── Dockerfile │ ├── src/ │ │ └── MyApp.Api/ │ │ ├── Program.cs │ │ ├── appsettings.json │ │ └── MyApp.Api.csproj │ └── MyApp.sln │ └── nginx/ # Reverse proxy config ├── nginx.conf └── ssl/ # SSL certificates
Dockerfile cho React
Sử dụng multi-stage build để tạo image nhẹ nhất có thể — stage 1 build React app, stage 2 chỉ chứa static files và Nginx:
# ========== Stage 1: Build ========== FROM node:20-alpine AS build WORKDIR /app # Copy package files trước để tận dụng Docker cache COPY package.json package-lock.json ./ RUN npm ci --silent # Copy source code và build COPY . . RUN npm run build # ========== Stage 2: Production ========== FROM nginx:alpine # Copy build output vào Nginx COPY --from=build /app/build /usr/share/nginx/html # Copy custom Nginx config cho SPA routing COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
File nginx.conf cho React SPA (xử lý client-side routing):
server { listen 80; root /usr/share/nginx/html; index index.html; # SPA: mọi route đều trả về index.html location / { try_files $uri $uri/ /index.html; } # Cache static assets location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } }
Dockerfile cho ASP.NET API
Tương tự React, sử dụng multi-stage build — stage 1 restore + publish, stage 2 chỉ chứa runtime:
# ========== Stage 1: Build ========== FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src # Copy csproj trước để tận dụng cache layer COPY src/MyApp.Api/MyApp.Api.csproj src/MyApp.Api/ RUN dotnet restore src/MyApp.Api/MyApp.Api.csproj # Copy toàn bộ source và publish COPY . . RUN dotnet publish src/MyApp.Api/MyApp.Api.csproj \ -c Release -o /app/publish --no-restore # ========== Stage 2: Runtime ========== FROM mcr.microsoft.com/dotnet/aspnet:8.0 WORKDIR /app # Chạy dưới non-root user cho bảo mật RUN adduser --disabled-password --gecos "" appuser USER appuser COPY --from=build /app/publish . EXPOSE 8080 ENTRYPOINT ["dotnet", "MyApp.Api.dll"]
.dockerignore
Giảm build context size và tránh copy files không cần thiết vào image:
# Dependencies node_modules/ **/bin/ **/obj/ # Build output build/ dist/ publish/ # Environment & secrets .env .env.* *.pfx *.pem # IDE & OS .vs/ .vscode/ .idea/ *.swp .DS_Store Thumbs.db # Git & Docker .git/ .gitignore docker-compose*.yml Dockerfile* README.md
docker-compose.yml
File quan trọng nhất — định nghĩa toàn bộ stack, networks, volumes, và dependencies:
services: # ── Nginx Reverse Proxy ── nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/ssl:/etc/nginx/ssl:ro depends_on: frontend: condition: service_started backend: condition: service_healthy networks: - frontend-net - backend-net restart: unless-stopped # ── React Frontend ── frontend: build: context: ./frontend dockerfile: Dockerfile networks: - frontend-net restart: unless-stopped # ── ASP.NET API ── backend: build: context: ./backend dockerfile: Dockerfile environment: - ASPNETCORE_ENVIRONMENT=Production - ConnectionStrings__DefaultConnection=${DB_CONNECTION} - Redis__ConnectionString=${REDIS_CONNECTION} depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - backend-net - data-net restart: unless-stopped # ── PostgreSQL ── postgres: image: postgres:16-alpine environment: - POSTGRES_DB=${POSTGRES_DB} - POSTGRES_USER=${POSTGRES_USER} - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"] interval: 10s timeout: 5s retries: 5 networks: - data-net restart: unless-stopped # ── Redis ── redis: image: redis:7-alpine command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - redisdata:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 networks: - data-net restart: unless-stopped # ── Networks ── networks: frontend-net: driver: bridge backend-net: driver: bridge data-net: driver: bridge internal: true # Không có internet access # ── Volumes ── volumes: pgdata: redisdata:
docker-compose.yml.
Luôn sử dụng biến môi trường từ file .env — và đảm bảo .env nằm trong
.gitignore.
Vòng đời Docker Compose
Các lệnh chính để quản lý stack:
# Build tất cả images docker compose build # Khởi chạy toàn bộ stack (background) docker compose up -d # Xem trạng thái các containers docker compose ps # Xem logs (follow mode) docker compose logs -f # Xem logs của một service cụ thể docker compose logs -f backend # Restart một service docker compose restart backend # Dừng toàn bộ stack docker compose down # Dừng và XÓA volumes (CẢNH BÁO: mất dữ liệu!) docker compose down -v
docker compose down -v sẽ xóa toàn bộ volumes bao gồm dữ liệu PostgreSQL. Chỉ
dùng khi bạn thực sự muốn reset mọi thứ. Trong production, hầu như không bao giờ nên dùng flag -v.
Triển khai lên server
Quy trình cơ bản để deploy lần đầu lên Ubuntu server:
# 1. SSH vào server ssh user@your-server-ip # 2. Clone source code git clone https://github.com/your-org/my-app.git cd my-app # 3. Tạo file .env từ template cp .env.example .env nano .env # Chỉnh sửa giá trị cho production # 4. Build và khởi chạy docker compose build --no-cache docker compose up -d # 5. Kiểm tra trạng thái docker compose ps docker compose logs -f --tail=50
git pull && docker compose build && docker compose up -d. Docker Compose sẽ chỉ restart các service
có image thay đổi.
Tại sao dùng Multi-stage Build?
So sánh image size giữa single-stage và multi-stage:
Single-stage
- React image: ~1.2 GB (chứa node_modules)
- API image: ~900 MB (chứa .NET SDK)
- Build tools không cần ở production
- Attack surface lớn hơn
Multi-stage
- React image: ~40 MB (Nginx + static files)
- API image: ~220 MB (.NET runtime only)
- Không có build tools, ít dependencies
- Attack surface nhỏ, khởi động nhanh