Compare commits

..

No commits in common. "0c9f5b58e031c48a7e3fd09b4b6ba8657427db12" and "20d0993b06bbdc276cf80d4b0b80b381674ba52e" have entirely different histories.

16 changed files with 87 additions and 290 deletions

View File

@ -39,18 +39,12 @@ jobs:
- name: Login to Container Registry - name: Login to Container Registry
run: echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USER" --password-stdin run: echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USER" --password-stdin
- name: Build and Push API image - name: Build and Push Docker image
run: | run: |
docker build -f Dockerfile.api -t "$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG" -t "$REGISTRY_URL/sortifal/pfee:latest" . docker build -t "$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG" -t "$REGISTRY_URL/sortifal/pfee:latest" .
docker push "$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG" docker push "$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG"
docker push "$REGISTRY_URL/sortifal/pfee:latest" docker push "$REGISTRY_URL/sortifal/pfee:latest"
- name: Build and Push Frontend image
run: |
docker build -f Dockerfile.frontend -t "$REGISTRY_URL/sortifal/pfee-frontend:$IMAGE_TAG" -t "$REGISTRY_URL/sortifal/pfee-frontend:latest" .
docker push "$REGISTRY_URL/sortifal/pfee-frontend:$IMAGE_TAG"
docker push "$REGISTRY_URL/sortifal/pfee-frontend:latest"
deploy: deploy:
name: Deploy to Kubernetes name: Deploy to Kubernetes
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -81,20 +75,12 @@ jobs:
-n sqdc-dashboard \ -n sqdc-dashboard \
--dry-run=client -o yaml | kubectl apply -f - --dry-run=client -o yaml | kubectl apply -f -
- name: Install Kustomize - name: Deploy to Kubernetes
run: | run: |
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash kubectl apply -f k8s/namespace.yaml k8s/deployment.yaml k8s/service.yaml k8s/ingress.yaml
sudo mv kustomize /usr/local/bin/
- name: Deploy with Kustomize - name: Update deployment and verify
run: | run: |
cd k8s kubectl set image deployment/sqdc-dashboard dashboard="$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG" -n sqdc-dashboard
kustomize edit set image gitea.vidoks.fr/sortifal/pfee="$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG" kubectl rollout status deployment/sqdc-dashboard -n sqdc-dashboard --timeout=5m
kustomize edit set image gitea.vidoks.fr/sortifal/pfee-frontend="$REGISTRY_URL/sortifal/pfee-frontend:$IMAGE_TAG"
kubectl apply -k .
- name: Verify deployment
run: |
kubectl rollout status deployment/sqdc-api -n sqdc-dashboard --timeout=5m
kubectl rollout status deployment/sqdc-frontend -n sqdc-dashboard --timeout=5m
kubectl get pods,svc,ingress -n sqdc-dashboard kubectl get pods,svc,ingress -n sqdc-dashboard

View File

@ -1,4 +1,5 @@
node_modules node_modules
build
.git .git
.gitignore .gitignore
*.md *.md

View File

@ -8,8 +8,8 @@ WORKDIR /app
# Copy package files # Copy package files
COPY package*.json ./ COPY package*.json ./
# Install all dependencies (including dev) needed for build # Install dependencies
RUN npm ci RUN npm ci --only=production
# Copy source code # Copy source code
COPY . . COPY . .
@ -17,9 +17,6 @@ COPY . .
# Build the application # Build the application
RUN npm run build RUN npm run build
# Install only production dependencies for runtime
RUN npm ci --only=production
# Stage 2: Production image with Nginx # Stage 2: Production image with Nginx
FROM nginx:alpine FROM nginx:alpine
@ -46,4 +43,4 @@ HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1 CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
# Start both nginx and the API server # Start both nginx and the API server
CMD ["sh", "-c", "node /app/server.js & nginx -g 'daemon off;'"] CMD sh -c "node /app/server.js & nginx -g 'daemon off;'"

View File

@ -1,24 +0,0 @@
# API Server Dockerfile
FROM node:18-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install only production dependencies
RUN npm ci --only=production
# Copy database and server files
COPY database ./database
COPY server.js .
# Expose API port
EXPOSE 3001
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3001/api/categories', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
# Start the API server
CMD ["node", "server.js"]

View File

@ -1,35 +0,0 @@
# Multi-stage build for React frontend with Nginx
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install all dependencies (including dev) needed for build
RUN npm ci
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Stage 2: Production image with Nginx
FROM nginx:alpine
# Copy custom nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy built application from builder stage
COPY --from=builder /app/build /usr/share/nginx/html
# Expose port
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
# Start nginx
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: sqdc-api
namespace: sqdc-dashboard
labels:
app: sqdc-api
spec:
type: ClusterIP
ports:
- port: 3001
targetPort: 3001
protocol: TCP
name: api
selector:
app: sqdc-api

View File

@ -1,58 +0,0 @@
apiVersion: batch/v1
kind: Job
metadata:
name: sqdc-db-init
namespace: sqdc-dashboard
labels:
app: sqdc-api
spec:
backoffLimit: 3
template:
metadata:
labels:
app: sqdc-api-init
spec:
serviceAccountName: sqdc-db-init
restartPolicy: Never
containers:
- name: db-init
image: gitea.vidoks.fr/sortifal/pfee:latest
imagePullPolicy: Always
command:
- sh
- -c
- |
echo "Starting database initialization..."
if [ ! -f /app/database/sqdc.db ]; then
echo "Creating new database from schema..."
sqlite3 /app/database/sqdc.db < /app/database/schema.sql
echo "Populating database with sample data..."
python3 /app/database/populate_db.py
echo "✅ Database initialized successfully"
else
echo "✅ Database already exists, skipping initialization"
fi
echo "Verifying database integrity..."
sqlite3 /app/database/sqdc.db "SELECT COUNT(*) as table_count FROM sqlite_master WHERE type='table';"
echo "Database initialization complete"
volumeMounts:
- name: database
mountPath: /app/database
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
volumes:
- name: database
persistentVolumeClaim:
claimName: sqdc-database-pvc
imagePullSecrets:
- name: registry-credentials

View File

@ -1,7 +0,0 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: sqdc-db-init
namespace: sqdc-dashboard
labels:
app: sqdc-api

View File

@ -1,25 +1,30 @@
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: sqdc-api name: sqdc-dashboard
namespace: sqdc-dashboard namespace: sqdc-dashboard
labels: labels:
app: sqdc-api app: sqdc-dashboard
spec: spec:
replicas: 2 replicas: 2
selector: selector:
matchLabels: matchLabels:
app: sqdc-api app: sqdc-dashboard
template: template:
metadata: metadata:
labels: labels:
app: sqdc-api app: sqdc-dashboard
spec: spec:
imagePullSecrets:
- name: registry-credentials
containers: containers:
- name: api - name: dashboard
image: gitea.vidoks.fr/sortifal/pfee:latest image: gitea.vidoks.fr/sortifal/pfee:latest
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 80
name: http
protocol: TCP
- containerPort: 3001 - containerPort: 3001
name: api name: api
protocol: TCP protocol: TCP
@ -28,23 +33,23 @@ spec:
value: "production" value: "production"
resources: resources:
requests: requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi" memory: "256Mi"
cpu: "250m" cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /api/categories path: /
port: 3001 port: 80
initialDelaySeconds: 30 initialDelaySeconds: 30
periodSeconds: 10 periodSeconds: 10
timeoutSeconds: 5 timeoutSeconds: 5
failureThreshold: 3 failureThreshold: 3
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /api/categories path: /
port: 3001 port: 80
initialDelaySeconds: 10 initialDelaySeconds: 10
periodSeconds: 5 periodSeconds: 5
timeoutSeconds: 3 timeoutSeconds: 3
@ -55,6 +60,17 @@ spec:
volumes: volumes:
- name: database - name: database
persistentVolumeClaim: persistentVolumeClaim:
claimName: sqdc-database-pvc claimName: sqdc-dashboard-pvc
imagePullSecrets: ---
- name: registry-credentials apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sqdc-dashboard-pvc
namespace: sqdc-dashboard
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: standard

View File

@ -1,50 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: sqdc-frontend
namespace: sqdc-dashboard
labels:
app: sqdc-frontend
spec:
replicas: 2
selector:
matchLabels:
app: sqdc-frontend
template:
metadata:
labels:
app: sqdc-frontend
spec:
containers:
- name: frontend
image: gitea.vidoks.fr/sortifal/pfee-frontend:latest
imagePullPolicy: Always
ports:
- containerPort: 80
name: http
protocol: TCP
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
imagePullSecrets:
- name: registry-credentials

View File

@ -1,16 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: sqdc-frontend
namespace: sqdc-dashboard
labels:
app: sqdc-frontend
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: sqdc-frontend

View File

@ -4,26 +4,18 @@ metadata:
name: sqdc-dashboard-ingress name: sqdc-dashboard-ingress
namespace: sqdc-dashboard namespace: sqdc-dashboard
annotations: annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/use-regex: "true"
spec: spec:
ingressClassName: nginx ingressClassName: nginx
rules: rules:
- host: diwii.sortifal.dev - host: diwii.sortifal.dev
http: http:
paths: paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: sqdc-api
port:
number: 3001
- path: / - path: /
pathType: Prefix pathType: Prefix
backend: backend:
service: service:
name: sqdc-frontend name: sqdc-dashboard-service
port: port:
number: 80 number: 80

View File

@ -5,24 +5,15 @@ namespace: sqdc-dashboard
resources: resources:
- namespace.yaml - namespace.yaml
- pvc.yaml - deployment.yaml
- db-init-sa.yaml - service.yaml
- db-init-job.yaml
- api-deployment.yaml
- api-service.yaml
- frontend-deployment.yaml
- frontend-service.yaml
- ingress.yaml - ingress.yaml
- configmap.yaml - configmap.yaml
commonLabels: commonLabels:
app: sqdc-dashboard
managed-by: kustomize managed-by: kustomize
# Image tags will be set via kustomize edit during deployment
images: images:
- name: gitea.vidoks.fr/sortifal/pfee - name: gitea.vidoks.fr/sortifal/pfee
newTag: latest newTag: latest
newName: gitea.vidoks.fr/sortifal/pfee
- name: gitea.vidoks.fr/sortifal/pfee-frontend
newTag: latest
newName: gitea.vidoks.fr/sortifal/pfee-frontend

View File

@ -1,13 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: sqdc-database-pvc
namespace: sqdc-dashboard
labels:
app: sqdc-api
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi

View File

@ -0,0 +1,20 @@
apiVersion: v1
kind: Service
metadata:
name: sqdc-dashboard-service
namespace: sqdc-dashboard
labels:
app: sqdc-dashboard
spec:
type: ClusterIP
selector:
app: sqdc-dashboard
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
- name: api
port: 3001
targetPort: 3001
protocol: TCP

View File

@ -1,8 +1,8 @@
server { server {
listen 80; listen 80;
server_name _; server_name localhost;
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html index.htm; index index.html;
# Gzip compression # Gzip compression
gzip on; gzip on;
@ -10,13 +10,20 @@ server {
gzip_min_length 1024; gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json; gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;
# Static assets with caching # API proxy
location /static/ { location /api/ {
expires 1y; proxy_pass http://localhost:3001/;
add_header Cache-Control "public, immutable"; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
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;
} }
# React app - handle client-side routing (must be last) # React app - handle client-side routing
location / { location / {
try_files $uri $uri/ /index.html; try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Cache-Control "no-cache, no-store, must-revalidate";
@ -24,6 +31,12 @@ server {
add_header Expires "0"; add_header Expires "0";
} }
# Static assets with caching
location /static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Security headers # Security headers
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always; add_header X-Content-Type-Options "nosniff" always;