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": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@elysiajs/cors": "1.4.2",
|
"@elysiajs/cors": "1.4.2",
|
||||||
"@zeavis/shared": "packages/shared",
|
"@zeavis/shared": "workspace:*",
|
||||||
"bcryptjs": "^3.0.3",
|
"bcryptjs": "^3.0.3",
|
||||||
"drizzle-orm": "^0.45.2",
|
"drizzle-orm": "^0.45.2",
|
||||||
"elysia": "^1.4.28",
|
"elysia": "^1.4.28",
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@tanstack/react-query": "^5.100.11",
|
"@tanstack/react-query": "^5.100.11",
|
||||||
"@vitejs/plugin-react": "^6.0.2",
|
"@vitejs/plugin-react": "^6.0.2",
|
||||||
"@zeavis/shared": "packages/shared",
|
"@zeavis/shared": "workspace:*",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^1.16.0",
|
"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=="],
|
"@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=="],
|
"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
|
.moon/cache
|
||||||
node_modules
|
node_modules
|
||||||
**/node_modules
|
**/node_modules
|
||||||
bun.lock
|
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
@@ -47,7 +46,8 @@ Machine_Learning/dataset
|
|||||||
Machine_Learning/dataset.zip
|
Machine_Learning/dataset.zip
|
||||||
Machine_Learning/dataset_*.zip
|
Machine_Learning/dataset_*.zip
|
||||||
Machine_Learning/model
|
Machine_Learning/model
|
||||||
Machine_Learning/best_model
|
Machine_Learning/best_model/*
|
||||||
|
!Machine_Learning/best_model/best_model.keras
|
||||||
apps/web/dist
|
apps/web/dist
|
||||||
apps/api/dist
|
apps/api/dist
|
||||||
__pycache__
|
__pycache__
|
||||||
@@ -55,6 +55,8 @@ __pycache__
|
|||||||
.pytest_cache
|
.pytest_cache
|
||||||
.venv
|
.venv
|
||||||
venv
|
venv
|
||||||
|
**/.venv
|
||||||
|
**/venv
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 2: Verify ignore file exists**
|
- [ ] **Step 2: Verify ignore file exists**
|
||||||
|
|||||||
Reference in New Issue
Block a user