feat: implement Kubernetes deployment infrastructure, migrate database to PostgreSQL, and add CI/CD pipeline
Some checks failed
Build & Deploy to K3s / build-and-deploy (push) Failing after 24s

This commit is contained in:
ordinarthur 2026-04-11 14:09:16 +02:00
parent 3bff3c8600
commit 21c92abc9c
8 changed files with 351 additions and 2 deletions

102
.gitea/workflows/deploy.yml Normal file
View File

@ -0,0 +1,102 @@
name: Build & Deploy to K3s
on:
push:
branches: [main]
env:
REGISTRY: git.arthurbarre.fr
BACKEND_IMAGE: git.arthurbarre.fr/ordinarthur/freedge-backend
FRONTEND_IMAGE: git.arthurbarre.fr/ordinarthur/freedge-frontend
REGISTRY_USER: ordinarthur
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Gitea Container Registry
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
docker login ${{ env.REGISTRY }} -u ${{ env.REGISTRY_USER }} --password-stdin
- name: Build backend image
run: |
docker build \
-t ${{ env.BACKEND_IMAGE }}:${{ github.sha }} \
-t ${{ env.BACKEND_IMAGE }}:latest \
./backend
- name: Build frontend image
run: |
docker build \
--build-arg VITE_API_BASE_URL=https://freedge.app/api \
--build-arg VITE_GOOGLE_CLIENT_ID=173866668387-i18igc0e1avqtsaqq6nig898bv6pvuk6.apps.googleusercontent.com \
-t ${{ env.FRONTEND_IMAGE }}:${{ github.sha }} \
-t ${{ env.FRONTEND_IMAGE }}:latest \
./frontend
- name: Push backend image
run: |
docker push ${{ env.BACKEND_IMAGE }}:${{ github.sha }}
docker push ${{ env.BACKEND_IMAGE }}:latest
- name: Push frontend image
run: |
docker push ${{ env.FRONTEND_IMAGE }}:${{ github.sha }}
docker push ${{ env.FRONTEND_IMAGE }}:latest
- name: Install kubectl
run: |
curl -LO "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
mv kubectl /usr/local/bin/kubectl
- name: Configure kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" | base64 -d > ~/.kube/config
- name: Apply namespace and shared resources
run: |
kubectl apply -f k8s/namespace.yml
kubectl apply -f k8s/configmap.yml
kubectl apply -f k8s/pvc.yml
kubectl apply -f k8s/service.yml
- name: Create image pull secret
run: |
kubectl -n freedge create secret docker-registry gitea-registry-secret \
--docker-server=${{ env.REGISTRY }} \
--docker-username=${{ env.REGISTRY_USER }} \
--docker-password="${{ secrets.REGISTRY_PASSWORD }}" \
--dry-run=client -o yaml | kubectl apply -f -
- name: Create app secrets
run: |
kubectl -n freedge create secret generic freedge-secrets \
--from-literal=DATABASE_URL="${{ secrets.DATABASE_URL }}" \
--from-literal=JWT_SECRET="${{ secrets.JWT_SECRET }}" \
--from-literal=OPENAI_API_KEY="${{ secrets.OPENAI_API_KEY }}" \
--from-literal=STRIPE_SECRET_KEY="${{ secrets.STRIPE_SECRET_KEY }}" \
--from-literal=STRIPE_WEBHOOK_SECRET="${{ secrets.STRIPE_WEBHOOK_SECRET }}" \
--from-literal=STRIPE_PRICE_ID_ESSENTIAL="${{ secrets.STRIPE_PRICE_ID_ESSENTIAL }}" \
--from-literal=STRIPE_PRICE_ID_PREMIUM="${{ secrets.STRIPE_PRICE_ID_PREMIUM }}" \
--dry-run=client -o yaml | kubectl apply -f -
- name: Deploy workloads
run: |
kubectl apply -f k8s/deployment.yml
kubectl -n freedge set image deployment/freedge-backend \
freedge-backend=${{ env.BACKEND_IMAGE }}:${{ github.sha }}
kubectl -n freedge set image deployment/freedge-frontend \
freedge-frontend=${{ env.FRONTEND_IMAGE }}:${{ github.sha }}
kubectl -n freedge rollout status deployment/freedge-backend --timeout=180s
kubectl -n freedge rollout status deployment/freedge-frontend --timeout=180s
kubectl -n freedge rollout status deployment/freedge-proxy --timeout=180s
- name: Cleanup old images
run: |
docker image prune -f

27
backend/Dockerfile Normal file
View File

@ -0,0 +1,27 @@
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npx prisma generate
RUN npm run build
RUN npm prune --omit=dev
FROM node:20-slim
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
COPY --from=build /app/package*.json ./
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/prisma ./prisma
COPY --from=build /app/dist ./dist
COPY --from=build /app/uploads ./uploads
EXPOSE 3000
CMD ["node", "dist/server.js"]

View File

@ -3,7 +3,7 @@ generator client {
}
datasource db {
provider = "sqlite"
provider = "postgresql"
url = env("DATABASE_URL")
}
@ -55,4 +55,4 @@ model Recipe {
user User @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
}

42
k8s/configmap.yml Normal file
View File

@ -0,0 +1,42 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: freedge-config
namespace: freedge
data:
NODE_ENV: "production"
PORT: "3000"
LOG_LEVEL: "info"
CORS_ORIGINS: "https://freedge.app"
FRONTEND_URL: "https://freedge.app"
PUBLIC_BASE_URL: "https://freedge.app/api"
OPENAI_TEXT_MODEL: "gpt-4o-mini"
OPENAI_TRANSCRIBE_MODEL: "gpt-4o-mini-transcribe"
ENABLE_IMAGE_GENERATION: "true"
OPENAI_IMAGE_MODEL: "gpt-image-1"
OPENAI_IMAGE_QUALITY: "medium"
OPENAI_IMAGE_SIZE: "1024x1024"
OPENAI_MAX_RETRIES: "3"
OPENAI_TIMEOUT_MS: "60000"
proxy.conf: |
server {
listen 80;
server_name _;
client_max_body_size 20M;
location /api/ {
rewrite ^/api/(.*) /$1 break;
proxy_pass http://freedge-backend:3000;
proxy_set_header Host $host;
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 https;
}
location / {
proxy_pass http://freedge-frontend:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

122
k8s/deployment.yml Normal file
View File

@ -0,0 +1,122 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: freedge-backend
namespace: freedge
spec:
replicas: 1
selector:
matchLabels:
app: freedge-backend
template:
metadata:
labels:
app: freedge-backend
spec:
imagePullSecrets:
- name: gitea-registry-secret
initContainers:
- name: prisma-db-push
image: git.arthurbarre.fr/ordinarthur/freedge-backend:latest
command: ["sh", "-c", "npx prisma db push --skip-generate"]
envFrom:
- configMapRef:
name: freedge-config
- secretRef:
name: freedge-secrets
volumeMounts:
- name: uploads
mountPath: /app/uploads
containers:
- name: freedge-backend
image: git.arthurbarre.fr/ordinarthur/freedge-backend:latest
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: freedge-config
- secretRef:
name: freedge-secrets
volumeMounts:
- name: uploads
mountPath: /app/uploads
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 30
volumes:
- name: uploads
persistentVolumeClaim:
claimName: freedge-uploads
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: freedge-frontend
namespace: freedge
spec:
replicas: 1
selector:
matchLabels:
app: freedge-frontend
template:
metadata:
labels:
app: freedge-frontend
spec:
imagePullSecrets:
- name: gitea-registry-secret
containers:
- name: freedge-frontend
image: git.arthurbarre.fr/ordinarthur/freedge-frontend:latest
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: freedge-proxy
namespace: freedge
spec:
replicas: 1
selector:
matchLabels:
app: freedge-proxy
template:
metadata:
labels:
app: freedge-proxy
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: proxy-config
mountPath: /etc/nginx/conf.d/default.conf
subPath: proxy.conf
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
volumes:
- name: proxy-config
configMap:
name: freedge-config

4
k8s/namespace.yml Normal file
View File

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: freedge

12
k8s/pvc.yml Normal file
View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: freedge-uploads
namespace: freedge
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 5Gi

40
k8s/service.yml Normal file
View File

@ -0,0 +1,40 @@
apiVersion: v1
kind: Service
metadata:
name: freedge-backend
namespace: freedge
spec:
selector:
app: freedge-backend
ports:
- name: http
port: 3000
targetPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: freedge-frontend
namespace: freedge
spec:
selector:
app: freedge-frontend
ports:
- name: http
port: 80
targetPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: freedge-proxy
namespace: freedge
spec:
type: NodePort
selector:
app: freedge-proxy
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30082