From 6e0177767881d311d3962901f7af79e437d558de Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Tue, 29 Apr 2025 21:36:54 +0100 Subject: [PATCH] ci pipeline and prod compose --- .github/workflows/ci.yml | 97 +++++++++++++++++++++++++++++ docker-compose.prod.yml | 128 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 docker-compose.prod.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9cf5e4c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,97 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + backend: + name: Backend + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: "pip" + + - name: Install dependencies + run: pip install -e ".[dev]" + + - name: Lint + run: | + ruff check . + ruff format --check . + + - name: Type check + run: mypy src/ + + - name: Test + env: + DATABASE_URL: "sqlite+aiosqlite:///:memory:" + run: pytest --cov=src --cov-report=term-missing --cov-fail-under=80 + + frontend: + name: Frontend + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Type check + run: npx tsc --noEmit + + - name: Test + run: npm test + + - name: Build + run: npm run build + + docker: + name: Docker Build + runs-on: ubuntu-latest + needs: [backend, frontend] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build backend image + uses: docker/build-push-action@v5 + with: + context: ./backend + push: false + tags: codetutor-backend:test + + - name: Build frontend image + uses: docker/build-push-action@v5 + with: + context: ./frontend + push: false + tags: codetutor-frontend:test diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..4d2da16 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,128 @@ +services: + traefik: + image: traefik:v3.0 + container_name: codetutor-traefik + command: + - --api.insecure=false + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --certificatesresolvers.letsencrypt.acme.httpchallenge=true + - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web + - --certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL:?ACME_EMAIL is required} + - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:ro + - codetutor-letsencrypt:/letsencrypt + restart: unless-stopped + healthcheck: + test: ["CMD", "traefik", "healthcheck"] + interval: 30s + timeout: 10s + retries: 3 + deploy: + resources: + limits: + cpus: "0.5" + memory: 256M + reservations: + cpus: "0.1" + memory: 64M + + db: + image: postgres:16-alpine + container_name: codetutor-db + environment: + POSTGRES_USER: ${POSTGRES_USER:-codetutor} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required} + POSTGRES_DB: ${POSTGRES_DB:-codetutor} + volumes: + - codetutor-db-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-codetutor}"] + interval: 10s + timeout: 5s + retries: 5 + restart: unless-stopped + deploy: + resources: + limits: + cpus: "1" + memory: 512M + reservations: + cpus: "0.25" + memory: 128M + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: codetutor-backend + environment: + DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-codetutor}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-codetutor} + CORS_ORIGINS: '["https://${DOMAIN:?DOMAIN is required}"]' + depends_on: + db: + condition: service_healthy + labels: + - traefik.enable=true + - traefik.http.routers.backend.rule=Host(`${DOMAIN}`) && PathPrefix(`/api`) + - traefik.http.routers.backend.entrypoints=websecure + - traefik.http.routers.backend.tls.certresolver=letsencrypt + - traefik.http.services.backend.loadbalancer.server.port=8000 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + restart: unless-stopped + deploy: + resources: + limits: + cpus: "1" + memory: 512M + reservations: + cpus: "0.25" + memory: 128M + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: codetutor-frontend + environment: + NEXT_PUBLIC_API_URL: https://${DOMAIN} + depends_on: + - backend + labels: + - traefik.enable=true + - traefik.http.routers.frontend.rule=Host(`${DOMAIN}`) + - traefik.http.routers.frontend.entrypoints=websecure + - traefik.http.routers.frontend.tls.certresolver=letsencrypt + - traefik.http.services.frontend.loadbalancer.server.port=3000 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + restart: unless-stopped + deploy: + resources: + limits: + cpus: "1" + memory: 512M + reservations: + cpus: "0.25" + memory: 128M + +volumes: + codetutor-db-data: + codetutor-letsencrypt: