Save current state before cleaning history
This commit is contained in:
parent
7f463ee287
commit
0167ffdd18
@ -1,12 +1,83 @@
|
|||||||
image: lachlanevenson/k8s-helm:latest
|
version: 1.0
|
||||||
|
|
||||||
stages:
|
workflow:
|
||||||
- deploy
|
name: Deploy to Production
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
variables:
|
jobs:
|
||||||
KUBECONFIG: /root/.kube/config # Adjust if you're mounting a kubeconfig differently
|
build-and-deploy:
|
||||||
|
name: Build and Deploy
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
deploy:
|
- name: Set up Docker Buildx
|
||||||
stage: deploy
|
uses: docker/setup-buildx-action@v2
|
||||||
script:
|
|
||||||
- helm upgrade --install fusero ./chart -f ./chart/values-prod.yaml
|
- name: Login to Docker Registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: registry.liquidrinu.com
|
||||||
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and Push Backend
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: ./backend
|
||||||
|
push: true
|
||||||
|
tags: registry.liquidrinu.com/fusero-backend:latest
|
||||||
|
cache-from: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache
|
||||||
|
cache-to: type=registry,ref=registry.liquidrinu.com/fusero-backend:buildcache,mode=max
|
||||||
|
|
||||||
|
- name: Build and Push Frontend
|
||||||
|
uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: ./frontend
|
||||||
|
push: true
|
||||||
|
tags: registry.liquidrinu.com/fusero-frontend:latest
|
||||||
|
cache-from: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache
|
||||||
|
cache-to: type=registry,ref=registry.liquidrinu.com/fusero-frontend:buildcache,mode=max
|
||||||
|
|
||||||
|
- name: Install kubectl
|
||||||
|
uses: azure/setup-kubectl@v3
|
||||||
|
with:
|
||||||
|
version: "latest"
|
||||||
|
|
||||||
|
- name: Setup kubeconfig
|
||||||
|
run: |
|
||||||
|
mkdir -p $HOME/.kube
|
||||||
|
echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > $HOME/.kube/config
|
||||||
|
chmod 600 $HOME/.kube/config
|
||||||
|
|
||||||
|
- name: Create secrets file
|
||||||
|
run: |
|
||||||
|
cat > ./chart/secrets.prod.yml << EOF
|
||||||
|
backend:
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: "${{ secrets.POSTGRES_PASSWORD }}"
|
||||||
|
DEFAULT_ADMIN_PASSWORD: "${{ secrets.DEFAULT_ADMIN_PASSWORD }}"
|
||||||
|
ENCRYPTION_KEY: "${{ secrets.ENCRYPTION_KEY }}"
|
||||||
|
JWT_SECRET: "${{ secrets.JWT_SECRET }}"
|
||||||
|
CHATGPT_API_KEY: "${{ secrets.CHATGPT_API_KEY }}"
|
||||||
|
CANVAS_API_KEY: "${{ secrets.CANVAS_API_KEY }}"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Deploy to Kubernetes
|
||||||
|
run: |
|
||||||
|
helm upgrade --install fusero ./chart \
|
||||||
|
--namespace fusero-prod \
|
||||||
|
--create-namespace \
|
||||||
|
--values ./chart/values.prod.yml \
|
||||||
|
--values ./chart/secrets.prod.yml \
|
||||||
|
--set backend.image.repository=registry.liquidrinu.com/fusero-backend \
|
||||||
|
--set frontend.image.repository=registry.liquidrinu.com/fusero-frontend
|
||||||
|
|
||||||
|
- name: Verify Deployment
|
||||||
|
run: |
|
||||||
|
kubectl rollout status deployment/fusero-backend -n fusero-prod
|
||||||
|
kubectl rollout status deployment/fusero-frontend -n fusero-prod
|
||||||
|
18
.gitignore
vendored
18
.gitignore
vendored
@ -124,3 +124,21 @@ secrets.yml
|
|||||||
values.dev.*
|
values.dev.*
|
||||||
values.prod.*
|
values.prod.*
|
||||||
|
|
||||||
|
# Secrets
|
||||||
|
chart/secrets.prod.yml
|
||||||
|
chart/secrets.*.yml
|
||||||
|
*.env
|
||||||
|
.env.*
|
||||||
|
|
||||||
|
# Development values with secrets
|
||||||
|
chart/values.dev.yml
|
||||||
|
|
||||||
|
# Production secrets
|
||||||
|
chart/secrets.prod.yml
|
||||||
|
chart/secrets.*.yml
|
||||||
|
|
||||||
|
# Keep templates and public configs
|
||||||
|
!chart/secrets.prod.template.yml
|
||||||
|
!chart/values.prod.template.yml
|
||||||
|
!chart/values.prod.public.yml
|
||||||
|
|
||||||
|
315
README.md
315
README.md
@ -11,15 +11,24 @@ A full-stack application boilerplate with a React frontend and Node.js backend
|
|||||||
- [📁 Project Structure](#-project-structure)
|
- [📁 Project Structure](#-project-structure)
|
||||||
- [⚙️ Prerequisites](#️-prerequisites)
|
- [⚙️ Prerequisites](#️-prerequisites)
|
||||||
- [💻 Development Setup](#-development-setup)
|
- [💻 Development Setup](#-development-setup)
|
||||||
|
- [To create a new migration:](#to-create-a-new-migration)
|
||||||
|
- [npm run migration:create](#npm-run-migrationcreate)
|
||||||
|
- [To apply migrations:](#to-apply-migrations)
|
||||||
|
- [To seed the database:](#to-seed-the-database)
|
||||||
- [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals)
|
- [Alternate: Running Services in Separate Terminals](#alternate-running-services-in-separate-terminals)
|
||||||
- [🛠️ Environment Setup](#️-environment-setup)
|
- [🛠️ Environment Setup](#️-environment-setup)
|
||||||
- [For Kubernetes, these are set in chart/values.yaml:](#for-kubernetes-these-are-set-in-chartvaluesyaml)
|
- [For Kubernetes, these are set in chart/values.yml:](#for-kubernetes-these-are-set-in-chartvaluesyml)
|
||||||
- [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db)
|
- [POSTGRES\_NAME=fusero-boilerplate-db](#postgres_namefusero-boilerplate-db)
|
||||||
- [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service)
|
- [POSTGRES\_HOSTNAME=postgres-service](#postgres_hostnamepostgres-service)
|
||||||
- [POSTGRES\_PORT=19095](#postgres_port19095)
|
- [POSTGRES\_PORT=19095](#postgres_port19095)
|
||||||
- [POSTGRES\_USER=root](#postgres_userroot)
|
- [POSTGRES\_USER=root](#postgres_userroot)
|
||||||
- [POSTGRES\_PASSWORD=root123](#postgres_passwordroot123)
|
- [POSTGRES\_PASSWORD=root123](#postgres_passwordroot123)
|
||||||
- [🚀 Production Deployment](#-production-deployment)
|
- [🐳 Docker Development](#-docker-development)
|
||||||
|
- [To create a new migration:](#to-create-a-new-migration-1)
|
||||||
|
- [npm run migration:create](#npm-run-migrationcreate-1)
|
||||||
|
- [To apply migrations:](#to-apply-migrations-1)
|
||||||
|
- [To seed the database:](#to-seed-the-database-1)
|
||||||
|
- [🚀 Kubernetes Deployment](#-kubernetes-deployment)
|
||||||
- [🌐 Frontend Routing in Production](#-frontend-routing-in-production)
|
- [🌐 Frontend Routing in Production](#-frontend-routing-in-production)
|
||||||
- [🔐 HTTPS with Self-Signed Certificates](#-https-with-self-signed-certificates)
|
- [🔐 HTTPS with Self-Signed Certificates](#-https-with-self-signed-certificates)
|
||||||
- [🧠 Development Best Practices](#-development-best-practices)
|
- [🧠 Development Best Practices](#-development-best-practices)
|
||||||
@ -53,17 +62,66 @@ A full-stack application boilerplate with a React frontend and Node.js backend
|
|||||||
- [Debugging Frontend Pod Crashes: NGINX SSL Certificate Errors](#debugging-frontend-pod-crashes-nginx-ssl-certificate-errors)
|
- [Debugging Frontend Pod Crashes: NGINX SSL Certificate Errors](#debugging-frontend-pod-crashes-nginx-ssl-certificate-errors)
|
||||||
- [How to fix for Kubernetes (Recommended)](#how-to-fix-for-kubernetes-recommended)
|
- [How to fix for Kubernetes (Recommended)](#how-to-fix-for-kubernetes-recommended)
|
||||||
- [Connecting to the Database from Your Host (DBeaver, etc.)](#connecting-to-the-database-from-your-host-dbeaver-etc)
|
- [Connecting to the Database from Your Host (DBeaver, etc.)](#connecting-to-the-database-from-your-host-dbeaver-etc)
|
||||||
|
- [🎯 Kubernetes Namespace Management](#-kubernetes-namespace-management)
|
||||||
|
- [Development Namespace Setup](#development-namespace-setup)
|
||||||
|
- [Production Namespace Setup](#production-namespace-setup)
|
||||||
|
- [Namespace Management Commands](#namespace-management-commands)
|
||||||
|
- [Recommended Kubernetes GUI Tools](#recommended-kubernetes-gui-tools)
|
||||||
|
- [🆕 Namespaced Development Environment with Helm](#-namespaced-development-environment-with-helm)
|
||||||
|
- [What was changed:](#what-was-changed)
|
||||||
|
- [How to use:](#how-to-use)
|
||||||
|
- [Why use namespaces?](#why-use-namespaces)
|
||||||
|
- [🔒 Production Security \& Best Practices](#-production-security--best-practices)
|
||||||
|
- [Environment Variables \& Secrets](#environment-variables--secrets)
|
||||||
|
- [HTTPS \& Certificates](#https--certificates)
|
||||||
|
- [CORS \& Security Headers](#cors--security-headers)
|
||||||
|
- [Logging \& Monitoring](#logging--monitoring)
|
||||||
|
- [Database Backup](#database-backup)
|
||||||
|
- [CI/CD \& Automated Testing](#cicd--automated-testing)
|
||||||
|
- [Troubleshooting Production](#troubleshooting-production)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📁 Project Structure
|
## 📁 Project Structure
|
||||||
|
|
||||||
fusero-app-boilerplate/
|
fusero-app-boilerplate/
|
||||||
├── frontend/ # React frontend application
|
├── chart/ # Helm chart for Kubernetes
|
||||||
├── backend/ # Node.js backend application
|
│ ├── Chart.yaml
|
||||||
├── docker-compose.yml # Production Docker configuration
|
│ ├── values.dev.yaml
|
||||||
├── docker-compose.dev.yml # Development Docker configuration
|
│ ├── values.prod.yml
|
||||||
└── chart/ # Helm chart for Kubernetes deployment
|
│ └── templates/
|
||||||
|
├── config/
|
||||||
|
├── coverage/
|
||||||
|
├── dist/
|
||||||
|
├── docs/
|
||||||
|
├── frontend/ # React frontend app
|
||||||
|
│ ├── public/
|
||||||
|
│ └── src/
|
||||||
|
├── mikro-orm.config.ts
|
||||||
|
├── nginx/
|
||||||
|
├── node_modules/
|
||||||
|
├── package.json
|
||||||
|
├── package-lock.json
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── docker-compose.dev.yml
|
||||||
|
├── .gitignore
|
||||||
|
├── .gitea-ci.yml
|
||||||
|
├── .prettierrc.json
|
||||||
|
├── .eslintrc.json
|
||||||
|
├── architecture.excalidraw
|
||||||
|
├── src/ # Node.js backend source
|
||||||
|
│ ├── apps/
|
||||||
|
│ ├── constants/
|
||||||
|
│ ├── database/
|
||||||
|
│ ├── middleware/
|
||||||
|
│ ├── plugins/
|
||||||
|
│ ├── shared/
|
||||||
|
│ ├── tests/
|
||||||
|
│ ├── types/
|
||||||
|
│ └── ...
|
||||||
|
├── test/
|
||||||
|
├── utils/
|
||||||
|
└── README.md
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -81,14 +139,18 @@ fusero-app-boilerplate/
|
|||||||
🗃️ PostgreSQL must run in Docker for consistent behavior.
|
🗃️ PostgreSQL must run in Docker for consistent behavior.
|
||||||
|
|
||||||
Create volume and start the database:
|
Create volume and start the database:
|
||||||
docker volume create fusero-db-data
|
docker volume create fusero-dev-db-data
|
||||||
docker-compose up -d db
|
docker-compose up -d db
|
||||||
|
|
||||||
Backend setup:
|
Backend setup:
|
||||||
cd backend
|
cd backend
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
npm install
|
npm install
|
||||||
|
# To create a new migration:
|
||||||
|
# npm run migration:create
|
||||||
|
# To apply migrations:
|
||||||
npm run migrate
|
npm run migrate
|
||||||
|
# To seed the database:
|
||||||
npm run seed
|
npm run seed
|
||||||
npm run dev &
|
npm run dev &
|
||||||
cd ..
|
cd ..
|
||||||
@ -130,7 +192,7 @@ POSTGRES_USER=root
|
|||||||
POSTGRES_PASSWORD=root123
|
POSTGRES_PASSWORD=root123
|
||||||
JWT_SECRET=your_jwt_secret_key_here
|
JWT_SECRET=your_jwt_secret_key_here
|
||||||
|
|
||||||
# For Kubernetes, these are set in chart/values.yaml:
|
# For Kubernetes, these are set in chart/values.yml:
|
||||||
# POSTGRES_NAME=fusero-boilerplate-db
|
# POSTGRES_NAME=fusero-boilerplate-db
|
||||||
# POSTGRES_HOSTNAME=postgres-service
|
# POSTGRES_HOSTNAME=postgres-service
|
||||||
# POSTGRES_PORT=19095
|
# POSTGRES_PORT=19095
|
||||||
@ -142,13 +204,47 @@ VITE_API_BASE_URL=http://localhost:14000/api/v1
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 Production Deployment
|
## 🐳 Docker Development
|
||||||
|
|
||||||
|
🗃️ PostgreSQL must run in Docker for consistent behavior.
|
||||||
|
|
||||||
|
Create volume and start the database:
|
||||||
|
docker volume create fusero-dev-db-data
|
||||||
|
docker-compose up -d db
|
||||||
|
|
||||||
|
Backend setup:
|
||||||
|
cd backend
|
||||||
|
cp .env.example .env
|
||||||
|
npm install
|
||||||
|
# To create a new migration:
|
||||||
|
# npm run migration:create
|
||||||
|
# To apply migrations:
|
||||||
|
npm run migrate
|
||||||
|
# To seed the database:
|
||||||
|
npm run seed
|
||||||
|
npm run dev &
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
Frontend setup:
|
||||||
|
cd frontend
|
||||||
|
cp .env.example .env
|
||||||
|
npm install
|
||||||
|
npm run dev &
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
App is running:
|
||||||
|
Frontend → http://localhost:3000
|
||||||
|
Backend → http://localhost:14000
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Kubernetes Deployment
|
||||||
|
|
||||||
1. Build and run with Docker:
|
1. Build and run with Docker:
|
||||||
docker-compose up --build
|
docker-compose up --build
|
||||||
|
|
||||||
2. Apply migrations and seed inside backend container:
|
2. Apply migrations and seed inside backend container:
|
||||||
docker exec -it fusero-app-backend npx mikro-orm migration:up
|
docker exec -it fusero-app-backend npm run migrate
|
||||||
docker exec -it fusero-app-backend npm run seed
|
docker exec -it fusero-app-backend npm run seed
|
||||||
|
|
||||||
3. Ensure all required environment variables are configured.
|
3. Ensure all required environment variables are configured.
|
||||||
@ -288,7 +384,7 @@ docker push <your-registry>/fusero-backend-dev:local
|
|||||||
|
|
||||||
### 3. Upgrade the Helm release with the latest values
|
### 3. Upgrade the Helm release with the latest values
|
||||||
```bash
|
```bash
|
||||||
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
|
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Restart the backend deployment to pick up new images and env vars
|
### 4. Restart the backend deployment to pick up new images and env vars
|
||||||
@ -334,7 +430,7 @@ docker push <your-registry>/fusero-frontend-dev:local
|
|||||||
|
|
||||||
### 3. Upgrade the Helm release
|
### 3. Upgrade the Helm release
|
||||||
```bash
|
```bash
|
||||||
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
|
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Restart the frontend deployment
|
### 4. Restart the frontend deployment
|
||||||
@ -386,7 +482,7 @@ Then rebuild and redeploy the frontend:
|
|||||||
```bash
|
```bash
|
||||||
docker build -t fusero-frontend-dev:local ./frontend
|
docker build -t fusero-frontend-dev:local ./frontend
|
||||||
# (push if needed)
|
# (push if needed)
|
||||||
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
|
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml
|
||||||
kubectl rollout restart deployment/fusero-frontend -n fusero
|
kubectl rollout restart deployment/fusero-frontend -n fusero
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -450,7 +546,7 @@ This means NGINX is trying to load an SSL certificate that does not exist in the
|
|||||||
3. (If using a remote registry) Push the image.
|
3. (If using a remote registry) Push the image.
|
||||||
4. Redeploy with Helm:
|
4. Redeploy with Helm:
|
||||||
```bash
|
```bash
|
||||||
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yaml
|
helm upgrade fusero ./chart -n fusero -f chart/values.dev.yml
|
||||||
```
|
```
|
||||||
5. Check pod status:
|
5. Check pod status:
|
||||||
```bash
|
```bash
|
||||||
@ -484,3 +580,190 @@ To connect to the Postgres database running in Kubernetes from your local machin
|
|||||||
4. **Test the connection.**
|
4. **Test the connection.**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🎯 Kubernetes Namespace Management
|
||||||
|
|
||||||
|
### Development Namespace Setup
|
||||||
|
```bash
|
||||||
|
# Create development namespace
|
||||||
|
kubectl create namespace fusero-dev
|
||||||
|
|
||||||
|
# Set current context to development namespace
|
||||||
|
kubectl config set-context --current --namespace=fusero-dev
|
||||||
|
|
||||||
|
# Deploy to development namespace
|
||||||
|
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Namespace Setup
|
||||||
|
```bash
|
||||||
|
# Create production namespace
|
||||||
|
kubectl create namespace fusero-prod
|
||||||
|
|
||||||
|
# Set current context to production namespace
|
||||||
|
kubectl config set-context --current --namespace=fusero-prod
|
||||||
|
|
||||||
|
# Deploy to production namespace
|
||||||
|
helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Namespace Management Commands
|
||||||
|
```bash
|
||||||
|
# List all namespaces
|
||||||
|
kubectl get namespaces
|
||||||
|
|
||||||
|
# Switch between namespaces
|
||||||
|
kubectl config set-context --current --namespace=<namespace-name>
|
||||||
|
|
||||||
|
# View resources in current namespace
|
||||||
|
kubectl get all
|
||||||
|
|
||||||
|
# Delete namespace (be careful!)
|
||||||
|
kubectl delete namespace <namespace-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended Kubernetes GUI Tools
|
||||||
|
1. **Lens** - The most popular Kubernetes IDE
|
||||||
|
- Download: https://k8slens.dev/
|
||||||
|
- Features:
|
||||||
|
- Real-time cluster monitoring
|
||||||
|
- Multi-cluster management
|
||||||
|
- Namespace isolation
|
||||||
|
- Resource visualization
|
||||||
|
- Log streaming
|
||||||
|
- Terminal access
|
||||||
|
|
||||||
|
2. **K9s** - Terminal-based UI
|
||||||
|
- Install: `brew install k9s` (Mac) or `scoop install k9s` (Windows)
|
||||||
|
- Features:
|
||||||
|
- Fast navigation
|
||||||
|
- Resource management
|
||||||
|
- Log viewing
|
||||||
|
- Port forwarding
|
||||||
|
|
||||||
|
3. **Octant** - Web-based UI
|
||||||
|
- Install: https://octant.dev/
|
||||||
|
- Features:
|
||||||
|
- Resource visualization
|
||||||
|
- Configuration management
|
||||||
|
- Log viewing
|
||||||
|
- Port forwarding
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆕 Namespaced Development Environment with Helm
|
||||||
|
|
||||||
|
You have configured your Kubernetes development environment to use a dedicated namespace (`fusero-dev`) for all core services (backend, frontend, and database). This ensures resource isolation and easier management between development and production.
|
||||||
|
|
||||||
|
### What was changed:
|
||||||
|
- All Kubernetes service templates (`backend-service.yml`, `frontend-service.yml`, `postgres-service.yml`) now use a namespace variable.
|
||||||
|
- The development values file (`chart/values.dev.yml`) sets `global.namespace: fusero-dev` and updates all internal service hostnames to use this namespace.
|
||||||
|
- Helm deployments now target the `fusero-dev` namespace for all dev resources.
|
||||||
|
|
||||||
|
### How to use:
|
||||||
|
1. **Create the development namespace (if not already created):**
|
||||||
|
```bash
|
||||||
|
kubectl create namespace fusero-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Deploy all services to the namespace:**
|
||||||
|
```bash
|
||||||
|
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Check running pods and services:**
|
||||||
|
```bash
|
||||||
|
kubectl get all -n fusero-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **If you update environment variables or service hostnames, repeat the Helm upgrade command.**
|
||||||
|
|
||||||
|
5. **If you see errors about immutable fields (e.g., for Jobs), delete the old Job before redeploying:**
|
||||||
|
```bash
|
||||||
|
kubectl delete job <job-name> -n fusero-dev
|
||||||
|
helm upgrade --install fusero ./chart -n fusero-dev -f chart/values.dev.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why use namespaces?
|
||||||
|
- Keeps dev and prod resources isolated
|
||||||
|
- Makes it easy to clean up or redeploy all dev resources
|
||||||
|
- Prevents accidental cross-environment access
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔒 Production Security & Best Practices
|
||||||
|
|
||||||
|
### Environment Variables & Secrets
|
||||||
|
|
||||||
|
The application uses a secure secrets management approach:
|
||||||
|
|
||||||
|
1. **Development Environment**:
|
||||||
|
- Use `.env` files locally (gitignored)
|
||||||
|
- Copy from `.env.example` as template
|
||||||
|
|
||||||
|
2. **Production Environment**:
|
||||||
|
- Secrets are managed through Gitea CI/CD secrets
|
||||||
|
- Template file: `chart/secrets.prod.template.yml`
|
||||||
|
- Actual secrets are generated during deployment
|
||||||
|
- Never commit actual secrets to the repository
|
||||||
|
|
||||||
|
3. **Required Secrets**:
|
||||||
|
- Database credentials
|
||||||
|
- Admin user credentials
|
||||||
|
- Security keys (encryption, JWT)
|
||||||
|
- API keys (ChatGPT, Canvas)
|
||||||
|
|
||||||
|
4. **Secrets in CI/CD**:
|
||||||
|
- Secrets are stored in Gitea CI/CD settings
|
||||||
|
- Automatically injected during deployment
|
||||||
|
- Used to generate `secrets.prod.yml` at runtime
|
||||||
|
|
||||||
|
5. **Security Best Practices**:
|
||||||
|
- All secrets files are gitignored
|
||||||
|
- Template files contain placeholder values
|
||||||
|
- Production secrets are never stored in the repository
|
||||||
|
- Regular rotation of secrets recommended
|
||||||
|
|
||||||
|
### HTTPS & Certificates
|
||||||
|
- In production, use trusted certificates (e.g., Let's Encrypt).
|
||||||
|
- Configure NGINX to enforce HTTPS.
|
||||||
|
|
||||||
|
### CORS & Security Headers
|
||||||
|
- Lock down CORS settings in production.
|
||||||
|
- Use security headers (e.g., HSTS, CSP).
|
||||||
|
|
||||||
|
### Logging & Monitoring
|
||||||
|
- Configure logging for production (e.g., ELK stack, Datadog).
|
||||||
|
- Set up basic monitoring (e.g., Prometheus, Grafana).
|
||||||
|
|
||||||
|
### Database Backup
|
||||||
|
- Regularly backup your production database.
|
||||||
|
- Example: Use `pg_dump` or a managed backup service.
|
||||||
|
|
||||||
|
### CI/CD & Automated Testing
|
||||||
|
- Run tests before deploying to production.
|
||||||
|
- Example CI/CD workflow:
|
||||||
|
```yaml
|
||||||
|
# .github/workflows/deploy.yml
|
||||||
|
name: Deploy to Production
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branch: main
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Run Tests
|
||||||
|
run: npm test
|
||||||
|
- name: Deploy to Kubernetes
|
||||||
|
run: helm upgrade --install fusero ./chart -n fusero-prod -f chart/values.prod.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting Production
|
||||||
|
- Common issues:
|
||||||
|
- Database connection errors: Check secrets and network policies.
|
||||||
|
- Pod crashes: Check logs with `kubectl logs <pod-name> -n fusero-prod`.
|
||||||
|
- Rollback: Use `helm rollback fusero <revision> -n fusero-prod`.
|
||||||
|
|
||||||
|
---
|
||||||
|
26
chart/secrets.prod.template.yml
Normal file
26
chart/secrets.prod.template.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
backend:
|
||||||
|
env:
|
||||||
|
# Database
|
||||||
|
POSTGRES_NAME: "fusero-boilerplate-db"
|
||||||
|
POSTGRES_HOSTNAME: "postgres-service.fusero-prod.svc.cluster.local"
|
||||||
|
POSTGRES_PORT: "5432"
|
||||||
|
POSTGRES_USER: "root"
|
||||||
|
POSTGRES_PASSWORD: "REPLACE_ME"
|
||||||
|
|
||||||
|
# Admin User
|
||||||
|
DEFAULT_ADMIN_USERNAME: "admin"
|
||||||
|
DEFAULT_ADMIN_EMAIL: "admin@fusero.nl"
|
||||||
|
DEFAULT_ADMIN_PASSWORD: "REPLACE_ME"
|
||||||
|
|
||||||
|
# Security
|
||||||
|
ENCRYPTION_KEY: "REPLACE_ME"
|
||||||
|
JWT_SECRET: "REPLACE_ME"
|
||||||
|
|
||||||
|
# API Keys
|
||||||
|
CHATGPT_API_KEY: "REPLACE_ME"
|
||||||
|
CANVAS_API_KEY: "REPLACE_ME"
|
||||||
|
CANVAS_API_URL: "https://talnet.instructure.com/api/v1"
|
||||||
|
|
||||||
|
# Server
|
||||||
|
FASTIFY_PORT: "14000"
|
||||||
|
NODE_ENV: "production"
|
@ -1,2 +0,0 @@
|
|||||||
# You can skip this if you manage secrets separately
|
|
||||||
# Included for completeness (values-dev.yaml handles them)
|
|
16
chart/templates/backend-secrets.yml
Normal file
16
chart/templates/backend-secrets.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# You can skip this if you manage secrets separately
|
||||||
|
# Included for completeness (values-dev.yaml handles them)
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: fusero-backend-secrets
|
||||||
|
namespace: {{ .Values.global.namespace }}
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
POSTGRES_PASSWORD: {{ .Values.backend.env.POSTGRES_PASSWORD | b64enc }}
|
||||||
|
DEFAULT_ADMIN_PASSWORD: {{ .Values.backend.env.DEFAULT_ADMIN_PASSWORD | b64enc }}
|
||||||
|
ENCRYPTION_KEY: {{ .Values.backend.env.ENCRYPTION_KEY | b64enc }}
|
||||||
|
JWT_SECRET: {{ .Values.backend.env.JWT_SECRET | b64enc }}
|
||||||
|
CHATGPT_API_KEY: {{ .Values.backend.env.CHATGPT_API_KEY | b64enc }}
|
||||||
|
CANVAS_API_KEY: {{ .Values.backend.env.CANVAS_API_KEY | b64enc }}
|
@ -2,6 +2,7 @@ apiVersion: v1
|
|||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: fusero-backend-service
|
name: fusero-backend-service
|
||||||
|
namespace: {{ .Values.global.namespace }}
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
app: fusero-backend
|
app: fusero-backend
|
@ -2,6 +2,7 @@ apiVersion: v1
|
|||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: fusero-frontend-service
|
name: fusero-frontend-service
|
||||||
|
namespace: {{ .Values.global.namespace }}
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
app: fusero-frontend
|
app: fusero-frontend
|
@ -2,6 +2,7 @@ apiVersion: v1
|
|||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: postgres-service
|
name: postgres-service
|
||||||
|
namespace: {{ .Values.global.namespace }}
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
app: postgres
|
app: postgres
|
53
chart/values.prod.public.yml
Normal file
53
chart/values.prod.public.yml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
global:
|
||||||
|
namespace: fusero-prod
|
||||||
|
security:
|
||||||
|
cors:
|
||||||
|
origin: "https://your-domain.com"
|
||||||
|
methods: "GET,POST,PUT,DELETE"
|
||||||
|
credentials: true
|
||||||
|
https:
|
||||||
|
enabled: true
|
||||||
|
certSecret: "fusero-tls-secret"
|
||||||
|
logging:
|
||||||
|
level: "info"
|
||||||
|
format: "json"
|
||||||
|
monitoring:
|
||||||
|
prometheus:
|
||||||
|
enabled: true
|
||||||
|
path: "/metrics"
|
||||||
|
|
||||||
|
backend:
|
||||||
|
image: fusero-backend:latest
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "200m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "512Mi"
|
||||||
|
env:
|
||||||
|
# Non-sensitive values only
|
||||||
|
NODE_ENV: "production"
|
||||||
|
FASTIFY_PORT: "14000"
|
||||||
|
CANVAS_API_URL: "https://talnet.instructure.com/api/v1"
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
image: fusero-frontend:latest
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "100m"
|
||||||
|
memory: "128Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "300m"
|
||||||
|
memory: "256Mi"
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
storage: 5Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "200m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "512Mi"
|
@ -1,22 +1,64 @@
|
|||||||
|
global:
|
||||||
|
namespace: fusero-prod
|
||||||
|
security:
|
||||||
|
cors:
|
||||||
|
origin: "https://app.fusero.nl"
|
||||||
|
methods: "GET,POST,PUT,DELETE"
|
||||||
|
credentials: true
|
||||||
|
https:
|
||||||
|
enabled: true
|
||||||
|
certSecret: "fusero-tls-secret"
|
||||||
|
logging:
|
||||||
|
level: "info"
|
||||||
|
format: "json"
|
||||||
|
monitoring:
|
||||||
|
prometheus:
|
||||||
|
enabled: true
|
||||||
|
path: "/metrics"
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
image: fusero-backend:latest
|
image: registry.liquidrinu.com/fusero-backend:latest
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "200m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "512Mi"
|
||||||
env:
|
env:
|
||||||
POSTGRES_HOST: postgres-service
|
POSTGRES_HOST: postgres-service
|
||||||
POSTGRES_PORT: "5432"
|
POSTGRES_PORT: "5432"
|
||||||
POSTGRES_NAME: fusero-db
|
POSTGRES_NAME: fusero-db
|
||||||
POSTGRES_USER: prod_admin
|
POSTGRES_USER: prod_admin
|
||||||
POSTGRES_PASSWORD: REPLACE_ME
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
DEFAULT_ADMIN_USERNAME: admin
|
DEFAULT_ADMIN_USERNAME: admin
|
||||||
DEFAULT_ADMIN_EMAIL: admin@fusero.nl
|
DEFAULT_ADMIN_EMAIL: admin@fusero.nl
|
||||||
DEFAULT_ADMIN_PASSWORD: STRONG_REPLACE_ME
|
DEFAULT_ADMIN_PASSWORD: ${DEFAULT_ADMIN_PASSWORD}
|
||||||
ENCRYPTION_KEY: PROD_REPLACE_ME_KEY
|
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
|
||||||
JWT_SECRET: PROD_REPLACE_ME_JWT
|
JWT_SECRET: ${JWT_SECRET}
|
||||||
CHATGPT_API_KEY: PROD_REPLACE_ME_CHATGPT
|
CHATGPT_API_KEY: ${CHATGPT_API_KEY}
|
||||||
CANVAS_API_KEY: PROD_REPLACE_ME_CANVAS
|
CANVAS_API_KEY: ${CANVAS_API_KEY}
|
||||||
|
CANVAS_API_URL: https://talnet.instructure.com/api/v1
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: fusero-frontend:latest
|
image: registry.liquidrinu.com/fusero-frontend:latest
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "100m"
|
||||||
|
memory: "128Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "300m"
|
||||||
|
memory: "256Mi"
|
||||||
|
env:
|
||||||
|
VITE_API_BASE_URL: https://app.fusero.nl
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15
|
image: postgres:15
|
||||||
storage: 5Gi
|
storage: 5Gi
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "200m"
|
||||||
|
memory: "256Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "512Mi"
|
||||||
|
30
package.json
30
package.json
@ -10,16 +10,25 @@
|
|||||||
"@": "src"
|
"@": "src"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"fc": "fastify cli",
|
"build:ts": "rimraf dist && tsc",
|
||||||
|
"build:frontend:dev": "docker build -t fusero-frontend-dev:local -f frontend/Dockerfile.dev ./frontend",
|
||||||
|
"prebuild": "npm run lint",
|
||||||
|
"start": "npm run build:ts && fastify start -l info -r tsconfig-paths/register dist/src/app.js",
|
||||||
|
"dev": "npm run build:ts && concurrently -k -p '[{name}]' -n 'TypeScript,App,Watcher' -c 'teal.bold,orange.bold,purple.bold' 'npm:watch:ts' 'npm:dev:start' 'npm:watch:ts'",
|
||||||
|
"dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P -r tsconfig-paths/register dist/src/app.js",
|
||||||
|
"watch:ts": "tsc -w",
|
||||||
|
"lint": "eslint src/**/*.{js,jsx,ts,tsx}",
|
||||||
|
"lint:fix": "eslint src/**/*.{js,jsx,ts,tsx} --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:unit": "tap test/services/**/*.ts",
|
"test:unit": "tap test/services/**/*.ts",
|
||||||
"test:integration": "tap test/integration/**/*.ts",
|
"test:integration": "tap test/integration/**/*.ts",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
"start": "npm run build:ts && fastify start -l info -r tsconfig-paths/register dist/src/app.js",
|
"test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts",
|
||||||
"prebuild": "npm run lint",
|
"migration:create": "npx mikro-orm migration:create",
|
||||||
"build:ts": "rimraf dist && tsc",
|
"migrate": "npx mikro-orm migration:up",
|
||||||
"build:frontend:dev": "docker build -t fusero-frontend-dev:local -f frontend/Dockerfile.dev ./frontend",
|
"seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts",
|
||||||
|
"app:generate": "node ./utils/generate-app.js",
|
||||||
"k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yml",
|
"k8s:dev:apply": "kubectl apply -f k8s/frontend/deployment.dev.yml",
|
||||||
"k8s:dev:delete": "kubectl delete deployment fusero-frontend-dev 2>/dev/null || true",
|
"k8s:dev:delete": "kubectl delete deployment fusero-frontend-dev 2>/dev/null || true",
|
||||||
"k8s:dev:run": "kubectl port-forward svc/fusero-frontend-service 3000:80",
|
"k8s:dev:run": "kubectl port-forward svc/fusero-frontend-service 3000:80",
|
||||||
@ -27,15 +36,6 @@
|
|||||||
"k8s:dev:svc": "kubectl describe svc fusero-frontend-service",
|
"k8s:dev:svc": "kubectl describe svc fusero-frontend-service",
|
||||||
"k8s:dev:deployment": "kubectl describe deployment fusero-frontend-dev",
|
"k8s:dev:deployment": "kubectl describe deployment fusero-frontend-dev",
|
||||||
"k8s:get": "kubectl get pods,svc,deployment",
|
"k8s:get": "kubectl get pods,svc,deployment",
|
||||||
"watch:ts": "tsc -w",
|
|
||||||
"dev": "npm run build:ts && concurrently -k -p \"[{name}]\" -n \"TypeScript,App\" -c \"yellow.bold,cyan.bold\" \"npm:watch:ts\" \"npm:dev:start\"",
|
|
||||||
"dev:start": "fastify start --ignore-watch=.ts$ -w -l info -P -r tsconfig-paths/register dist/src/app.js",
|
|
||||||
"app:generate": "node ./utils/generate-app.js",
|
|
||||||
"lint": "eslint src/**/*.{js,jsx,ts,tsx}",
|
|
||||||
"lint:fix": "eslint src/**/*.{js,jsx,ts,tsx} --fix",
|
|
||||||
"migration:create": "npx mikro-orm migration:create",
|
|
||||||
"seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts",
|
|
||||||
"test:db": "ts-node -r tsconfig-paths/register src/database/test-connection.ts",
|
|
||||||
"k8s:exec": "kubectl exec -it $POD_NAME -- /bin/sh"
|
"k8s:exec": "kubectl exec -it $POD_NAME -- /bin/sh"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@ -103,4 +103,4 @@
|
|||||||
"git add"
|
"git add"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -129,6 +129,11 @@ const app: FastifyPluginAsync = async (app, opts): Promise<void> => {
|
|||||||
export async function buildApp(): Promise<FastifyInstance> {
|
export async function buildApp(): Promise<FastifyInstance> {
|
||||||
const server = fastify();
|
const server = fastify();
|
||||||
await server.register(app);
|
await server.register(app);
|
||||||
|
|
||||||
|
// Set the port to 14000
|
||||||
|
const port = process.env.PORT || 14000;
|
||||||
|
await server.listen({ port: Number(port), host: '0.0.0.0' });
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user