hoàn thành docker compose giai đoạn 1

This commit is contained in:
2026-03-31 16:29:03 +07:00
parent 5baea90d61
commit 7da144286f
8 changed files with 211 additions and 2 deletions
+5
View File
@@ -17,3 +17,8 @@ ACCESS_TOKEN_EXPIRE_MINUTES=60
# ── Ports ─────────────────────────────────────────────────────────────────────
# Port exposed on the host for the Next.js frontend
FRONTEND_PORT=3000
# ── Docker Hub images (optional — leave blank to build locally) ───────────────
# Set these on the target machine so docker compose pull works without building
# BACKEND_IMAGE=your_dockerhub_username/lms-backend:latest
# FRONTEND_IMAGE=your_dockerhub_username/lms-frontend:latest
+3 -1
View File
@@ -1,5 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Response, status
from sqlalchemy.orm import Session
import os
from ..database import get_db
from ..dependencies import get_current_user
@@ -16,6 +17,7 @@ router = APIRouter(prefix="/auth", tags=["auth"])
_COOKIE_NAME = "access_token"
_COOKIE_MAX_AGE = ACCESS_TOKEN_EXPIRE_MINUTES * 60 # seconds
_COOKIE_SECURE = os.getenv("COOKIE_SECURE", "false").lower() == "true"
def _set_auth_cookie(response: Response, user_id: int) -> None:
@@ -24,7 +26,7 @@ def _set_auth_cookie(response: Response, user_id: int) -> None:
key=_COOKIE_NAME,
value=token,
httponly=True,
secure=True, # send only over HTTPS (Nginx handles TLS in prod)
secure=_COOKIE_SECURE,
samesite="lax",
max_age=_COOKIE_MAX_AGE,
path="/",
Executable
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
# Pull LMS images from GHCR and start the stack on a new machine.
# Usage: ./deploy.sh
# Requires: .env file with BACKEND_IMAGE, FRONTEND_IMAGE, POSTGRES_PASSWORD, JWT_SECRET_KEY
set -e
cd "$(dirname "$0")"
if [ ! -f .env ]; then
echo "❌ .env not found. Copy .env.example → .env and fill in the values."
exit 1
fi
source .env
if [ -z "$POSTGRES_PASSWORD" ] || [ "$POSTGRES_PASSWORD" = "change_me_strong_password" ]; then
echo "❌ Set POSTGRES_PASSWORD in .env"
exit 1
fi
if [ -z "$JWT_SECRET_KEY" ] || [ "$JWT_SECRET_KEY" = "change_me_generate_with_secrets_token_hex_32" ]; then
echo "❌ Set JWT_SECRET_KEY in .env"
echo " Generate: python3 -c \"import secrets; print(secrets.token_hex(32))\""
exit 1
fi
# Login GHCR if token is provided
if [ -n "$GITHUB_TOKEN" ] && [ -n "$GITHUB_USER" ]; then
echo "🔐 Logging in to ghcr.io..."
echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_USER" --password-stdin
fi
echo "📦 Pulling images from GHCR..."
docker compose pull
echo "🚀 Starting stack..."
docker compose up -d
echo ""
echo "✅ Running! Open http://localhost:${FRONTEND_PORT:-3000}"
docker compose ps
+2
View File
@@ -20,6 +20,7 @@ services:
# ── FastAPI backend ───────────────────────────────────────────────────────────
backend:
image: ${BACKEND_IMAGE:-lms-backend:latest}
build:
context: ./backend
dockerfile: Dockerfile
@@ -42,6 +43,7 @@ services:
# ── Next.js frontend ──────────────────────────────────────────────────────────
frontend:
image: ${FRONTEND_IMAGE:-lms-frontend:latest}
build:
context: ./frontend
dockerfile: Dockerfile
+1
View File
@@ -27,6 +27,7 @@ ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/next.config.js ./
EXPOSE 3000
+13
View File
@@ -0,0 +1,13 @@
declare module "fabric" {
export const fabric: any;
}
declare module "pdfjs-dist" {
export const GlobalWorkerOptions: {
workerSrc: string;
};
export const version: string;
export function getDocument(params: any): {
promise: Promise<any>;
};
}
Executable
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# Push LMS images to GitHub Container Registry (GHCR)
# Usage: ./push-ghcr.sh [tag]
# Reads GITHUB_USER and GITHUB_TOKEN from .env
set -e
cd "$(dirname "$0")"
# Load .env
if [ -f .env ]; then
export $(grep -v '^#' .env | grep -E 'GITHUB_USER|GITHUB_TOKEN' | xargs)
fi
TAG="${1:-latest}"
GITHUB_USER="${GITHUB_USER:?GITHUB_USER not set in .env}"
GITHUB_TOKEN="${GITHUB_TOKEN:?GITHUB_TOKEN not set in .env}"
BACKEND_IMAGE="ghcr.io/${GITHUB_USER}/lms-backend:${TAG}"
FRONTEND_IMAGE="ghcr.io/${GITHUB_USER}/lms-frontend:${TAG}"
echo "========================================"
echo " LMS → GHCR Push"
echo " Backend : $BACKEND_IMAGE"
echo " Frontend: $FRONTEND_IMAGE"
echo "========================================"
echo ""
# ── 1. Login to GHCR ──────────────────────────────────────────────────────────
echo "🔐 Logging in to ghcr.io ..."
echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_USER" --password-stdin
# ── 2. Build images ───────────────────────────────────────────────────────────
echo ""
echo "🔨 Building images..."
cd "$(dirname "$0")"
docker compose build
# ── 3. Tag for GHCR ───────────────────────────────────────────────────────────
echo ""
echo "🏷 Tagging images..."
docker tag lms-backend:latest "$BACKEND_IMAGE"
docker tag lms-frontend:latest "$FRONTEND_IMAGE"
# ── 4. Push ───────────────────────────────────────────────────────────────────
echo ""
echo "⬆ Pushing to GHCR..."
docker push "$BACKEND_IMAGE"
docker push "$FRONTEND_IMAGE"
echo ""
echo "✅ Done! Images pushed:"
echo " $BACKEND_IMAGE"
echo " $FRONTEND_IMAGE"
echo ""
echo "📋 Add these to .env on the target machine:"
echo " BACKEND_IMAGE=$BACKEND_IMAGE"
echo " FRONTEND_IMAGE=$FRONTEND_IMAGE"
echo ""
echo "🚀 On the target machine:"
echo " docker compose pull && docker compose up -d"
+82
View File
@@ -52,3 +52,85 @@ Let's build this step-by-step to avoid context limits.
1. First, analyze this spec and confirm you understand.
2. Provide the database schema models (SQLAlchemy or Prisma).
3. Wait for my confirmation before writing the Backend API routes.
---
## 7. CI/CD — Push lên GitHub Container Registry (GHCR)
### 7.1. Yêu cầu
- **Docker** đã cài và đang chạy
- **GitHub Personal Access Token (PAT)** với quyền `write:packages``read:packages`
- Tạo tại: https://github.com/settings/tokens → *Generate new token (classic)*
- File `.env` đã được cấu hình (xem `.env.example`)
### 7.2. Cấu hình `.env`
Thêm các dòng sau vào file `.env` (file này đã được gitignore, **không commit**):
```env
GITHUB_USER=<github_username_của_bạn>
GITHUB_TOKEN=<personal_access_token>
BACKEND_IMAGE=ghcr.io/<github_username>/lms-backend:latest
FRONTEND_IMAGE=ghcr.io/<github_username>/lms-frontend:latest
```
### 7.3. Build & Push lên GHCR
```bash
chmod +x push-ghcr.sh
./push-ghcr.sh
```
Script sẽ tự động:
1. Đọc `GITHUB_USER``GITHUB_TOKEN` từ `.env`
2. Đăng nhập vào `ghcr.io`
3. Build cả hai image (`lms-backend`, `lms-frontend`) bằng `docker compose build`
4. Tag và push lên GHCR
Để push với tag cụ thể (ví dụ: version):
```bash
./push-ghcr.sh v1.0.0
```
### 7.4. Deploy trên máy khác
Trên máy đích (server, VPS, máy tính khác):
```bash
# 1. Copy các file cần thiết
scp docker-compose.yml .env.example deploy.sh user@server:/opt/lms/
ssh user@server
# 2. Tạo .env từ example
cd /opt/lms
cp .env.example .env
# Điền các giá trị: POSTGRES_PASSWORD, JWT_SECRET_KEY, GITHUB_USER, GITHUB_TOKEN,
# BACKEND_IMAGE, FRONTEND_IMAGE
# 3. Chạy deploy
chmod +x deploy.sh
./deploy.sh
```
Script `deploy.sh` sẽ:
1. Kiểm tra `.env` hợp lệ
2. Đăng nhập GHCR (nếu có token)
3. Pull image từ GHCR
4. Khởi động toàn bộ stack: `db`, `backend`, `frontend`
### 7.5. Kiểm tra packages trên GitHub
Sau khi push, image sẽ xuất hiện tại:
```
https://github.com/<github_username>?tab=packages
```
> **Lưu ý:** Mặc định packages ở chế độ **Private**. Để máy khác pull mà không cần token, vào
> GitHub → Packages → tên package → *Package settings* → đổi visibility sang **Public**.
### 7.6. Sinh JWT Secret Key
```bash
python3 -c "import secrets; print(secrets.token_hex(32))"
```