feat: add Docker deployment workflow
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
5ca95907a0
commit
1978597bcf
@@ -0,0 +1,26 @@
|
||||
.git
|
||||
.github
|
||||
.claude
|
||||
.moon/cache
|
||||
node_modules
|
||||
**/node_modules
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
Dockerfile
|
||||
docker-compose.yml
|
||||
Machine_Learning/dataset
|
||||
Machine_Learning/dataset.zip
|
||||
Machine_Learning/dataset_*.zip
|
||||
Machine_Learning/model
|
||||
Machine_Learning/best_model/*
|
||||
!Machine_Learning/best_model/best_model.keras
|
||||
apps/web/dist
|
||||
apps/api/dist
|
||||
__pycache__
|
||||
*.pyc
|
||||
.pytest_cache
|
||||
.venv
|
||||
venv
|
||||
**/.venv
|
||||
**/venv
|
||||
@@ -0,0 +1,98 @@
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_PREFIX: ghcr.io/${{ github.repository }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
service:
|
||||
- name: web
|
||||
dockerfile: apps/web/Dockerfile
|
||||
- name: api
|
||||
dockerfile: apps/api/Dockerfile
|
||||
- name: ml
|
||||
dockerfile: apps/ml-service/Dockerfile
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_PREFIX }}/${{ matrix.service.name }}
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=sha
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ${{ matrix.service.dockerfile }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha,scope=${{ matrix.service.name }}
|
||||
cache-to: type=gha,mode=max,scope=${{ matrix.service.name }}
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/main'
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
steps:
|
||||
- name: Deploy to VPS
|
||||
uses: appleboy/ssh-action@master
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_PORT || 22 }}
|
||||
script: |
|
||||
set -e
|
||||
cd /opt/ZeaVis-Edu
|
||||
git pull origin main
|
||||
|
||||
cat > .env << 'ENVEOF'
|
||||
GITHUB_REPOSITORY=${{ github.repository }}
|
||||
DATABASE_URL=${{ secrets.DATABASE_URL }}
|
||||
SESSION_SECRET=${{ secrets.SESSION_SECRET }}
|
||||
WEB_APP_URL=https://zeavisedu.asepharyana.tech
|
||||
ML_SERVICE_URL=https://ml.zeavisedu.asepharyana.tech
|
||||
ENVEOF
|
||||
|
||||
docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||
docker network create app-shared-net 2>/dev/null || true
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
docker compose ps
|
||||
docker compose ps | grep -q "zeavis-web.*Up" || exit 1
|
||||
docker compose ps | grep -q "zeavis-api.*Up" || exit 1
|
||||
docker compose ps | grep -q "zeavis-ml.*Up" || exit 1
|
||||
@@ -0,0 +1,21 @@
|
||||
FROM oven/bun:1.3.14 AS deps
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock bunfig.toml tsconfig.base.json ./
|
||||
COPY apps/api/package.json apps/api/package.json
|
||||
COPY apps/web/package.json apps/web/package.json
|
||||
COPY packages/shared/package.json packages/shared/package.json
|
||||
RUN bun install --frozen-lockfile --production
|
||||
|
||||
FROM oven/bun:1.3.14 AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV API_PORT=3000
|
||||
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY package.json bun.lock bunfig.toml tsconfig.base.json ./
|
||||
COPY apps/api apps/api
|
||||
COPY packages/shared packages/shared
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["bun", "apps/api/src/index.ts"]
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM python:3.11-slim AS runner
|
||||
|
||||
WORKDIR /app
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV MODEL_PATH=/app/model/best_model.keras
|
||||
ENV MODEL_INPUT_SIZE=224
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends libgomp1 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY apps/ml-service/requirements.txt ./requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY apps/ml-service ./
|
||||
COPY Machine_Learning/best_model/best_model.keras /app/model/best_model.keras
|
||||
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM oven/bun:1.3.14 AS builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json bun.lock bunfig.toml tsconfig.base.json ./
|
||||
COPY packages/shared/package.json packages/shared/package.json
|
||||
COPY apps/web/package.json apps/web/package.json
|
||||
COPY apps/api/package.json apps/api/package.json
|
||||
RUN bun install --frozen-lockfile
|
||||
|
||||
COPY packages/shared packages/shared
|
||||
COPY apps/web apps/web
|
||||
RUN bun run --cwd packages/shared build
|
||||
RUN bun run --cwd apps/web build
|
||||
|
||||
FROM nginx:1.27-alpine AS runner
|
||||
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /app/apps/web/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -0,0 +1,18 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass https://api.zeavisedu.asepharyana.tech/api/;
|
||||
proxy_set_header Host api.zeavisedu.asepharyana.tech;
|
||||
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;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"lockfileVersion": 0,
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
@@ -12,7 +13,7 @@
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@elysiajs/cors": "1.4.2",
|
||||
"@zeavis/shared": "packages/shared",
|
||||
"@zeavis/shared": "workspace:*",
|
||||
"bcryptjs": "^3.0.3",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"elysia": "^1.4.28",
|
||||
@@ -32,7 +33,7 @@
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tanstack/react-query": "^5.100.11",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"@zeavis/shared": "packages/shared",
|
||||
"@zeavis/shared": "workspace:*",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^1.16.0",
|
||||
@@ -250,11 +251,11 @@
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.2", "", { "dependencies": { "@rolldown/pluginutils": "^1.0.0" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg=="],
|
||||
|
||||
"@zeavis/api": ["@zeavis/api@workspace:apps/api", { "dependencies": { "@elysiajs/cors": "1.4.2", "@zeavis/shared": "packages/shared", "bcryptjs": "^3.0.3", "drizzle-orm": "^0.45.2", "elysia": "^1.4.28", "postgres": "^3.4.9" }, "devDependencies": { "@types/bcryptjs": "^3.0.0", "@types/bun": "latest", "drizzle-kit": "^0.31.10", "typescript": "^6.0.3" } }],
|
||||
"@zeavis/api": ["@zeavis/api@workspace:apps/api"],
|
||||
|
||||
"@zeavis/shared": ["@zeavis/shared@workspace:packages/shared", { "devDependencies": { "typescript": "^6.0.3" } }],
|
||||
"@zeavis/shared": ["@zeavis/shared@workspace:packages/shared"],
|
||||
|
||||
"@zeavis/web": ["@zeavis/web@workspace:apps/web", { "dependencies": { "@radix-ui/react-slot": "^1.2.4", "@tanstack/react-query": "^5.100.11", "@vitejs/plugin-react": "^6.0.2", "@zeavis/shared": "packages/shared", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^1.16.0", "react": "^19.2.6", "react-dom": "^19.2.6", "react-router-dom": "^7.15.1", "tailwind-merge": "^3.6.0", "zustand": "^5.0.13" }, "devDependencies": { "@tailwindcss/postcss": "^4.3.0", "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", "postcss": "^8.5.15", "typescript": "^6.0.3", "vite": "^8.0.14" } }],
|
||||
"@zeavis/web": ["@zeavis/web@workspace:apps/web"],
|
||||
|
||||
"bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="],
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
networks:
|
||||
app-shared-net:
|
||||
external: true
|
||||
name: app-shared-net
|
||||
|
||||
services:
|
||||
web:
|
||||
image: ghcr.io/${GITHUB_REPOSITORY:-mytheclipse/zeavis-edu}/web:main
|
||||
container_name: zeavis-web
|
||||
restart: always
|
||||
networks:
|
||||
- app-shared-net
|
||||
env_file:
|
||||
- .env
|
||||
labels:
|
||||
traefik.enable: "true"
|
||||
traefik.http.routers.zeavis-web.rule: Host(`zeavisedu.asepharyana.tech`)
|
||||
traefik.http.routers.zeavis-web.entrypoints: websecure
|
||||
traefik.http.routers.zeavis-web.tls: "true"
|
||||
traefik.http.routers.zeavis-web.tls.certresolver: letsencrypt
|
||||
traefik.http.services.zeavis-web.loadbalancer.server.port: "80"
|
||||
|
||||
api:
|
||||
image: ghcr.io/${GITHUB_REPOSITORY:-mytheclipse/zeavis-edu}/api:main
|
||||
container_name: zeavis-api
|
||||
restart: always
|
||||
networks:
|
||||
- app-shared-net
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
API_PORT: "3000"
|
||||
WEB_APP_URL: https://zeavisedu.asepharyana.tech
|
||||
ML_SERVICE_URL: https://ml.zeavisedu.asepharyana.tech
|
||||
labels:
|
||||
traefik.enable: "true"
|
||||
traefik.http.routers.zeavis-api.rule: Host(`api.zeavisedu.asepharyana.tech`)
|
||||
traefik.http.routers.zeavis-api.entrypoints: websecure
|
||||
traefik.http.routers.zeavis-api.tls: "true"
|
||||
traefik.http.routers.zeavis-api.tls.certresolver: letsencrypt
|
||||
traefik.http.services.zeavis-api.loadbalancer.server.port: "3000"
|
||||
|
||||
ml:
|
||||
image: ghcr.io/${GITHUB_REPOSITORY:-mytheclipse/zeavis-edu}/ml:main
|
||||
container_name: zeavis-ml
|
||||
restart: always
|
||||
networks:
|
||||
- app-shared-net
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
MODEL_PATH: /app/model/best_model.keras
|
||||
MODEL_INPUT_SIZE: "224"
|
||||
labels:
|
||||
traefik.enable: "true"
|
||||
traefik.http.routers.zeavis-ml.rule: Host(`ml.zeavisedu.asepharyana.tech`)
|
||||
traefik.http.routers.zeavis-ml.entrypoints: websecure
|
||||
traefik.http.routers.zeavis-ml.tls: "true"
|
||||
traefik.http.routers.zeavis-ml.tls.certresolver: letsencrypt
|
||||
traefik.http.services.zeavis-ml.loadbalancer.server.port: "8000"
|
||||
@@ -37,7 +37,6 @@ Write this exact file:
|
||||
.moon/cache
|
||||
node_modules
|
||||
**/node_modules
|
||||
bun.lock
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -47,7 +46,8 @@ Machine_Learning/dataset
|
||||
Machine_Learning/dataset.zip
|
||||
Machine_Learning/dataset_*.zip
|
||||
Machine_Learning/model
|
||||
Machine_Learning/best_model
|
||||
Machine_Learning/best_model/*
|
||||
!Machine_Learning/best_model/best_model.keras
|
||||
apps/web/dist
|
||||
apps/api/dist
|
||||
__pycache__
|
||||
@@ -55,6 +55,8 @@ __pycache__
|
||||
.pytest_cache
|
||||
.venv
|
||||
venv
|
||||
**/.venv
|
||||
**/venv
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify ignore file exists**
|
||||
|
||||
Reference in New Issue
Block a user