diff --git a/k8s/.DS_Store b/k8s/.DS_Store new file mode 100644 index 0000000..fe78278 Binary files /dev/null and b/k8s/.DS_Store differ diff --git a/k8s/.dockerignore b/k8s/.dockerignore new file mode 100644 index 0000000..ccc8d67 --- /dev/null +++ b/k8s/.dockerignore @@ -0,0 +1,262 @@ +# Dependency directories +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +.env.local +.env.development.local +.env.test.local +.env.production.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +public + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Git +.git +.gitignore + +# Documentation +README.md +*.md + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Docker files +Dockerfile* +.dockerignore + +# Kubernetes files +k8s/ +*.yaml +*.yml + +# CI/CD files +.github/ +.gitlab-ci.yml +.circleci/ + +# Testing +test/ +tests/ +**/*.test.js +**/*.test.ts +**/*.spec.js +**/*.spec.ts + +# Development tools +.eslintrc* +.prettierrc* +jest.config.* +babel.config.* +webpack.config.* + +# Build artifacts +build/ +dist/ +out/ + +# Package manager files +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Environment files +.env* +!.env.example diff --git a/k8s/README.md b/k8s/README.md new file mode 100644 index 0000000..ea800bf --- /dev/null +++ b/k8s/README.md @@ -0,0 +1,289 @@ +# Back2Trade Kubernetes Deployment + +Production-ready Kubernetes deployment for the Back2Trade trading application using Helm charts. + +## Architecture + +- **Frontend**: Next.js application (Port 3000) +- **Backend**: NestJS API server (Port 3001) +- **Database**: PostgreSQL (Port 5432) + +## Prerequisites + +- Kubernetes cluster (Minikube, Docker Desktop, or cloud) +- Helm 3.x +- kubectl configured +- Docker (for custom builds) + +## Quick Deployment + +```bash +# Clone repository +git clone +cd back-2-trade + +# Deploy with Helm +helm install back2trade k8s/back-2-trade-chart + +# Check status +kubectl get pods -l app.kubernetes.io/instance=back2trade + +# Access applications +kubectl port-forward deployment/back2trade-frontend 3000:3000 +kubectl port-forward deployment/back2trade-backend 3001:3001 +``` + +## Project Structure + +``` +back-2-trade/ +├── back2trade/ +│ ├── api/app_api/ # NestJS Backend source +│ └── web2/back2trade/ # Next.js Frontend source +├── k8s/ +│ ├── backend/Dockerfile # Backend container +│ ├── frontend/Dockerfile # Frontend container +│ └── back-2-trade-chart/ # Helm chart +│ ├── values.yaml # Configuration +│ └── templates/ # K8s manifests +└── README.md +``` + +## Configuration + +### Default Settings (values.yaml) + +```yaml +# Replicas +replicaCount: 2 + +# Backend +backend: + image: + repository: YOUR_REGISTRY/back2trade-backend + tag: latest + env: + NODE_ENV: production + DATABASE_URL: "postgresql://back2trade:password@back2trade-database:5432/back2trade" + +# Frontend +frontend: + image: + repository: YOUR_REGISTRY/back2trade-frontend + tag: latest + env: + NODE_ENV: development + NEXT_PUBLIC_API_URL: "http://back2trade-backend:3001/api" + +# Database +database: + env: + POSTGRES_DB: "back2trade" + POSTGRES_USER: "back2trade" + POSTGRES_PASSWORD: "password" + persistence: + size: 1Gi +``` + +### Environment Overrides + +```bash +# Development +helm upgrade back2trade k8s/back-2-trade-chart \ + --set frontend.env.NODE_ENV=development + +# Production +helm upgrade back2trade k8s/back-2-trade-chart \ + --set frontend.env.NODE_ENV=production \ + --set database.persistence.size=10Gi +``` + +## Registry Configuration + +Before deployment, configure your Docker registry: + +```bash +# Option 1: Use environment variable +export DOCKER_REGISTRY=your-registry.com + +# Option 2: Update values.yaml directly +sed -i 's/YOUR_REGISTRY/your-registry.com/g' k8s/back-2-trade-chart/values.yaml + +# Option 3: Override during deployment +helm upgrade back2trade k8s/back-2-trade-chart \ + --set backend.image.repository=your-registry.com/back2trade-backend \ + --set frontend.image.repository=your-registry.com/back2trade-frontend +``` + +## Operations + +### Deployment Commands + +```bash +# Install +helm install back2trade k8s/back-2-trade-chart + +# Upgrade +helm upgrade back2trade k8s/back-2-trade-chart + +# Uninstall +helm uninstall back2trade + +# Rollback +helm rollback back2trade 1 +``` + +### Monitoring + +```bash +# Pod status +kubectl get pods -l app.kubernetes.io/instance=back2trade + +# Logs +kubectl logs -l app.kubernetes.io/component=backend -f +kubectl logs -l app.kubernetes.io/component=frontend -f + +# Resource usage +kubectl top pods -l app.kubernetes.io/instance=back2trade + +# Detailed pod info +kubectl describe pod +``` + +### Scaling + +```bash +# Scale replicas +helm upgrade back2trade k8s/back-2-trade-chart --set replicaCount=3 + +# Or directly +kubectl scale deployment back2trade-backend --replicas=3 +``` + +## Docker Images + +### Pre-built Images +- Backend: `YOUR_REGISTRY/back2trade-backend:latest` +- Frontend: `YOUR_REGISTRY/back2trade-frontend:latest` +- Database: `postgres:15-alpine` + +### Custom Builds + +```bash +# Backend +cd k8s/backend +docker build -t your-registry/back2trade-backend:latest -f Dockerfile ../../ +docker push your-registry/back2trade-backend:latest + +# Frontend +cd k8s/frontend +docker build -t your-registry/back2trade-frontend:latest -f Dockerfile ../../ +docker push your-registry/back2trade-frontend:latest + +# Update deployment +helm upgrade back2trade k8s/back-2-trade-chart \ + --set backend.image.repository=your-registry/back2trade-backend \ + --set frontend.image.repository=your-registry/back2trade-frontend +``` + +## Troubleshooting + +### Common Issues + +**Frontend pods not ready** +- Symptoms: `0/1 Ready`, readiness probe failures +- Cause: Next.js dev server listening on localhost only +- Solution: See `FRONTEND_TROUBLESHOOTING.md` + +**Backend database connection errors** +```bash +kubectl logs | grep -i database +kubectl get pvc # Check persistent volume +``` + +**Image pull errors** +```bash +# Verify images exist +docker pull YOUR_REGISTRY/back2trade-backend:latest + +# Force pull +helm upgrade back2trade k8s/back-2-trade-chart \ + --set backend.image.pullPolicy=Always +``` + +**Database pod stuck pending** +```bash +# Check storage (Minikube needs smaller size) +helm upgrade back2trade k8s/back-2-trade-chart \ + --set database.persistence.size=1Gi +``` + +### Health Checks + +```bash +# Quick status +kubectl get pods,svc,pvc -l app.kubernetes.io/instance=back2trade + +# Application health +curl http://localhost:3001/health # Backend via port-forward +curl http://localhost:3000 # Frontend via port-forward +``` + +## Security + +### Default Credentials +- Database: `back2trade:password` + +### Production Checklist +- Change default database password +- Use Kubernetes secrets for sensitive data +- Set resource limits +- Enable network policies +- Use non-root containers + +## Services + +- Frontend: `back2trade-frontend:3000` +- Backend: `back2trade-backend:3001` +- Database: `back2trade-database:5432` + +## Known Issues + +1. **Frontend readiness probes failing** - Application works but pods show not ready +2. **Mixed dev/prod configuration** - Frontend uses development mode, backend uses production + +See `FRONTEND_TROUBLESHOOTING.md` and `DEPLOYMENT_STATUS.md` for detailed solutions. + +## CI/CD Example + +```yaml +# .github/workflows/deploy.yml +name: Deploy +on: + push: + branches: [main] +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build and Deploy + run: | + docker build -t registry/backend:${{ github.sha }} k8s/backend + docker push registry/backend:${{ github.sha }} + helm upgrade back2trade k8s/back-2-trade-chart \ + --set backend.image.tag=${{ github.sha }} +``` + +## Support + +1. Check `DEPLOYMENT_STATUS.md` for current issues +2. Review pod logs and events +3. Consult troubleshooting guides +4. Contact DevOps team with deployment details + +--- + +Last Updated: September 2025 +Kubernetes: 1.24+ +Helm: 3.x \ No newline at end of file diff --git a/k8s/back-2-trade-chart/.DS_Store b/k8s/back-2-trade-chart/.DS_Store new file mode 100644 index 0000000..555e4cc Binary files /dev/null and b/k8s/back-2-trade-chart/.DS_Store differ diff --git a/k8s/back-2-trade-chart/.helmignore b/k8s/back-2-trade-chart/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/k8s/back-2-trade-chart/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/k8s/back-2-trade-chart/Chart.yaml b/k8s/back-2-trade-chart/Chart.yaml new file mode 100644 index 0000000..ee348fe --- /dev/null +++ b/k8s/back-2-trade-chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: back2trade +description: Helm Chart dla aplikacji back2trade - React + Next.js frontend oraz Nest.js backend +type: application +version: 0.1.0 +appVersion: "1.0" diff --git a/k8s/back-2-trade-chart/templates/NOTES.txt b/k8s/back-2-trade-chart/templates/NOTES.txt new file mode 100644 index 0000000..095d90f --- /dev/null +++ b/k8s/back-2-trade-chart/templates/NOTES.txt @@ -0,0 +1,46 @@ +1. Get the application URL by running these commands: + +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else }} + # Frontend Service + export FRONTEND_POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "back2trade.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=frontend" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:3000 to use your frontend application" + kubectl --namespace {{ .Release.Namespace }} port-forward $FRONTEND_POD_NAME 3000:3000 + +{{- if .Values.backend.enabled }} + # Backend Service + export BACKEND_POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "back2trade.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=backend" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:3001 to access your backend API" + kubectl --namespace {{ .Release.Namespace }} port-forward $BACKEND_POD_NAME 3001:3001 +{{- end }} + +{{- if .Values.database.enabled }} + # Database Service + export DATABASE_POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "back2trade.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=database" -o jsonpath="{.items[0].metadata.name}") + echo "Connect to PostgreSQL database on localhost:5432" + kubectl --namespace {{ .Release.Namespace }} port-forward $DATABASE_POD_NAME 5432:5432 + +{{- end }} +{{- end }} + +2. Check the status of your deployment: + kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/instance={{ .Release.Name }}" + +3. View logs from your application: + # Frontend logs + kubectl logs --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/component=frontend" -f + +{{- if .Values.backend.enabled }} + # Backend logs + kubectl logs --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/component=backend" -f +{{- end }} + +{{- if .Values.database.enabled }} + # Database logs + kubectl logs --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/component=database" -f +{{- end }} diff --git a/k8s/back-2-trade-chart/templates/_helpers.tpl b/k8s/back-2-trade-chart/templates/_helpers.tpl new file mode 100644 index 0000000..2762fe6 --- /dev/null +++ b/k8s/back-2-trade-chart/templates/_helpers.tpl @@ -0,0 +1,83 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "back2trade.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "back2trade.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "back2trade.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "back2trade.labels" -}} +helm.sh/chart: {{ include "back2trade.chart" . }} +{{ include "back2trade.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "back2trade.selectorLabels" -}} +app.kubernetes.io/name: {{ include "back2trade.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "back2trade.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "back2trade.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the backend service name +*/}} +{{- define "back2trade.backendServiceName" -}} +{{- printf "%s-backend" (include "back2trade.fullname" .) }} +{{- end }} + +{{/* +Create the frontend service name +*/}} +{{- define "back2trade.frontendServiceName" -}} +{{- printf "%s-frontend" (include "back2trade.fullname" .) }} +{{- end }} + +{{/* +Create the database service name +*/}} +{{- define "back2trade.databaseServiceName" -}} +{{- printf "%s-database" (include "back2trade.fullname" .) }} +{{- end }} diff --git a/k8s/back-2-trade-chart/templates/backend-deployment.yaml b/k8s/back-2-trade-chart/templates/backend-deployment.yaml new file mode 100644 index 0000000..ec4289a --- /dev/null +++ b/k8s/back-2-trade-chart/templates/backend-deployment.yaml @@ -0,0 +1,91 @@ +{{- if .Values.backend.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "back2trade.fullname" . }}-backend + labels: + {{- include "back2trade.labels" . | nindent 4 }} + app.kubernetes.io/component: backend + annotations: + deployment.kubernetes.io/revision: {{ .Release.Revision | quote }} + kubectl.kubernetes.io/restartedAt: {{ now | quote }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "back2trade.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: backend + template: + metadata: + labels: + {{- include "back2trade.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: backend + annotations: + kubectl.kubernetes.io/restartedAt: {{ now | quote }} + checksum/config: {{ .Values | toYaml | sha256sum }} + spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "back2trade.serviceAccountName" . }} + containers: + - name: backend + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 10 }} + {{- end }} + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }}" + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.backend.service.port }} + protocol: TCP + env: + - name: NODE_ENV + value: {{ .Values.backend.env.NODE_ENV | quote }} + - name: PORT + value: {{ .Values.backend.env.PORT | quote }} + - name: DATABASE_URL + value: "postgresql://{{ .Values.database.env.POSTGRES_USER }}:{{ .Values.database.env.POSTGRES_PASSWORD }}@{{ include "back2trade.fullname" . }}-database:{{ .Values.database.service.port }}/{{ .Values.database.env.POSTGRES_DB }}?schema=public" + - name: PRISMA_SEED + value: "false" + {{- if .Values.secrets.finnhub.token }} + - name: FINNHUB_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "back2trade.fullname" . }}-secrets + key: finnhub-token + {{- end }} + {{- if .Values.secrets.finnhub.api }} + - name: FINNHUB_API + valueFrom: + secretKeyRef: + name: {{ include "back2trade.fullname" . }}-secrets + key: finnhub-api + {{- end }} + {{- if and .Values.healthChecks .Values.healthChecks.backend .Values.healthChecks.backend.livenessProbe }} + livenessProbe: + {{- toYaml .Values.healthChecks.backend.livenessProbe | nindent 10 }} + {{- end }} + {{- if and .Values.healthChecks .Values.healthChecks.backend .Values.healthChecks.backend.readinessProbe }} + readinessProbe: + {{- toYaml .Values.healthChecks.backend.readinessProbe | nindent 10 }} + {{- end }} + {{- with .Values.resources.backend }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/backend-service.yaml b/k8s/back-2-trade-chart/templates/backend-service.yaml new file mode 100644 index 0000000..a861e49 --- /dev/null +++ b/k8s/back-2-trade-chart/templates/backend-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.backend.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "back2trade.fullname" . }}-backend + labels: + {{- include "back2trade.labels" . | nindent 4 }} + app.kubernetes.io/component: backend +spec: + type: {{ .Values.backend.service.type }} + ports: + - port: {{ .Values.backend.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "back2trade.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: backend +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/database-deployment.yaml b/k8s/back-2-trade-chart/templates/database-deployment.yaml new file mode 100644 index 0000000..42b26e6 --- /dev/null +++ b/k8s/back-2-trade-chart/templates/database-deployment.yaml @@ -0,0 +1,101 @@ +{{- if .Values.database.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "back2trade.fullname" . }}-database + labels: + {{- include "back2trade.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + replicas: 1 + selector: + matchLabels: + {{- include "back2trade.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: database + template: + metadata: + labels: + {{- include "back2trade.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: database + spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "back2trade.serviceAccountName" . }} + containers: + - name: postgres + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 10 }} + {{- end }} + image: "{{ .Values.database.image.repository }}:{{ .Values.database.image.tag }}" + imagePullPolicy: {{ .Values.database.image.pullPolicy }} + ports: + - name: postgres + containerPort: {{ .Values.database.service.port }} + protocol: TCP + env: + - name: POSTGRES_DB + value: {{ .Values.database.env.POSTGRES_DB | quote }} + - name: POSTGRES_USER + value: {{ .Values.database.env.POSTGRES_USER | quote }} + - name: POSTGRES_PASSWORD + value: {{ .Values.database.env.POSTGRES_PASSWORD | quote }} + - name: PGDATA + value: "/var/lib/postgresql/data/pgdata" + livenessProbe: + exec: + command: + - /bin/sh + - -c + - exec pg_isready -U "{{ .Values.database.env.POSTGRES_USER }}" -d "{{ .Values.database.env.POSTGRES_DB }}" -h 127.0.0.1 -p {{ .Values.database.service.port }} + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 6 + readinessProbe: + exec: + command: + - /bin/sh + - -c + - -e + - | + exec pg_isready -U "{{ .Values.database.env.POSTGRES_USER }}" -d "{{ .Values.database.env.POSTGRES_DB }}" -h 127.0.0.1 -p {{ .Values.database.service.port }} + [ -f /opt/bitnami/postgresql/tmp/.initialized ] || [ -f /bitnami/postgresql/.initialized ] + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + successThreshold: 1 + failureThreshold: 6 + {{- with .Values.resources.database }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + subPath: postgres + volumes: + {{- if .Values.database.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ include "back2trade.fullname" . }}-database-pvc + {{- else }} + - name: data + emptyDir: {} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/database-pvc.yaml b/k8s/back-2-trade-chart/templates/database-pvc.yaml new file mode 100644 index 0000000..3653b84 --- /dev/null +++ b/k8s/back-2-trade-chart/templates/database-pvc.yaml @@ -0,0 +1,22 @@ +{{- if and .Values.database.enabled .Values.database.persistence.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "back2trade.fullname" . }}-database-pvc + labels: + {{- include "back2trade.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.database.persistence.size }} + {{- if .Values.database.persistence.storageClass }} + {{- if (eq "-" .Values.database.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: "{{ .Values.database.persistence.storageClass }}" + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/database-service.yaml b/k8s/back-2-trade-chart/templates/database-service.yaml new file mode 100644 index 0000000..c34ac00 --- /dev/null +++ b/k8s/back-2-trade-chart/templates/database-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.database.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "back2trade.fullname" . }}-database + labels: + {{- include "back2trade.labels" . | nindent 4 }} + app.kubernetes.io/component: database +spec: + type: {{ .Values.database.service.type }} + ports: + - port: {{ .Values.database.service.port }} + targetPort: postgres + protocol: TCP + name: postgres + selector: + {{- include "back2trade.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: database +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/frontend-deployment.yaml b/k8s/back-2-trade-chart/templates/frontend-deployment.yaml new file mode 100644 index 0000000..d7849dc --- /dev/null +++ b/k8s/back-2-trade-chart/templates/frontend-deployment.yaml @@ -0,0 +1,75 @@ +{{- if .Values.frontend.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "back2trade.fullname" . }}-frontend + labels: + {{- include "back2trade.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend + annotations: + deployment.kubernetes.io/revision: {{ .Release.Revision | quote }} + kubectl.kubernetes.io/restartedAt: {{ now | quote }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "back2trade.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: frontend + template: + metadata: + labels: + {{- include "back2trade.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: frontend + annotations: + kubectl.kubernetes.io/restartedAt: {{ now | quote }} + checksum/config: {{ .Values | toYaml | sha256sum }} + spec: + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "back2trade.serviceAccountName" . }} + containers: + - name: frontend + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 10 }} + {{- end }} + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }}" + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.frontend.service.port }} + protocol: TCP + env: + - name: NODE_ENV + value: {{ .Values.frontend.env.NODE_ENV | quote }} + - name: PORT + value: {{ .Values.frontend.env.PORT | quote }} + - name: NEXT_PUBLIC_API_URL + value: "http://{{ include "back2trade.fullname" . }}-backend:3001/api" + {{- if and .Values.healthChecks .Values.healthChecks.frontend .Values.healthChecks.frontend.livenessProbe }} + livenessProbe: + {{- toYaml .Values.healthChecks.frontend.livenessProbe | nindent 10 }} + {{- end }} + {{- if and .Values.healthChecks .Values.healthChecks.frontend .Values.healthChecks.frontend.readinessProbe }} + readinessProbe: + {{- toYaml .Values.healthChecks.frontend.readinessProbe | nindent 10 }} + {{- end }} + {{- with .Values.resources.frontend }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/frontend-service.yaml b/k8s/back-2-trade-chart/templates/frontend-service.yaml new file mode 100644 index 0000000..d96718e --- /dev/null +++ b/k8s/back-2-trade-chart/templates/frontend-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.frontend.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "back2trade.fullname" . }}-frontend + labels: + {{- include "back2trade.labels" . | nindent 4 }} + app.kubernetes.io/component: frontend +spec: + type: {{ .Values.frontend.service.type }} + ports: + - port: {{ .Values.frontend.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "back2trade.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: frontend +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/ingress.yaml b/k8s/back-2-trade-chart/templates/ingress.yaml new file mode 100644 index 0000000..74aa9f6 --- /dev/null +++ b/k8s/back-2-trade-chart/templates/ingress.yaml @@ -0,0 +1,60 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "back2trade.fullname" . -}} +{{- $svcPort := .Values.frontend.service.port -}} +{{- $backendSvcPort := .Values.backend.service.port -}} +{{- if and .Values.ingress.className (not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class")) }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "back2trade.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }}-{{ .service }} + port: + number: {{ if eq .service "frontend" }}{{ $svcPort }}{{ else }}{{ $backendSvcPort }}{{ end }} + {{- else }} + serviceName: {{ $fullName }}-{{ .service }} + servicePort: {{ if eq .service "frontend" }}{{ $svcPort }}{{ else }}{{ $backendSvcPort }}{{ end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/prisma-migration-job.yaml b/k8s/back-2-trade-chart/templates/prisma-migration-job.yaml new file mode 100644 index 0000000..0051893 --- /dev/null +++ b/k8s/back-2-trade-chart/templates/prisma-migration-job.yaml @@ -0,0 +1,87 @@ +{{- if and .Values.backend.enabled .Values.prisma.migrationJob.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "back2trade.fullname" . }}-prisma-migration + labels: + {{- include "back2trade.labels" . | nindent 4 }} + app.kubernetes.io/component: prisma-migration + annotations: + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-5" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + template: + metadata: + labels: + {{- include "back2trade.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: prisma-migration + spec: + restartPolicy: Never + {{- with .Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "back2trade.serviceAccountName" . }} + containers: + - name: prisma-migration + {{- with .Values.securityContext }} + securityContext: + {{- toYaml . | nindent 10 }} + {{- end }} + image: "{{ .Values.prisma.migrationJob.image.repository }}:{{ .Values.prisma.migrationJob.image.tag }}" + imagePullPolicy: {{ .Values.prisma.migrationJob.image.pullPolicy }} + command: ["/bin/sh"] + args: + - -c + - | + echo "Starting Prisma migration job..." + + # Wait for database to be ready + echo "Waiting for database connection..." + until npx prisma db push --accept-data-loss --force-reset; do + echo "Database is unavailable - sleeping" + sleep 5 + done + + echo "Database is ready!" + + # Generate Prisma client + echo "Generating Prisma client..." + npx prisma generate + + # Run database migrations + echo "Running Prisma migrations..." + npx prisma migrate deploy + + {{- if .Values.prisma.migrations.seed }} + # Seed database if enabled + echo "Seeding database..." + npx prisma db seed + {{- end }} + + echo "Prisma migration job completed successfully!" + env: + - name: DATABASE_URL + value: "postgresql://{{ .Values.database.env.POSTGRES_USER }}:{{ .Values.database.env.POSTGRES_PASSWORD }}@{{ include "back2trade.fullname" . }}-database:{{ .Values.database.service.port }}/{{ .Values.database.env.POSTGRES_DB }}?schema=public" + - name: NODE_ENV + value: "production" + {{- with .Values.resources.backend }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + backoffLimit: 3 + ttlSecondsAfterFinished: 120 +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/secret.yaml b/k8s/back-2-trade-chart/templates/secret.yaml new file mode 100644 index 0000000..bfc688a --- /dev/null +++ b/k8s/back-2-trade-chart/templates/secret.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "back2trade.fullname" . }}-secrets + labels: + {{- include "back2trade.labels" . | nindent 4 }} +type: Opaque +data: + {{- if .Values.secrets.finnhub.token }} + finnhub-token: {{ .Values.secrets.finnhub.token | b64enc }} + {{- end }} + {{- if .Values.secrets.finnhub.api }} + finnhub-api: {{ .Values.secrets.finnhub.api | b64enc }} + {{- end }} + {{- if .Values.secrets.database.url }} + database-url: {{ .Values.secrets.database.url | b64enc }} + {{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/templates/serviceaccount.yaml b/k8s/back-2-trade-chart/templates/serviceaccount.yaml new file mode 100644 index 0000000..c1252b5 --- /dev/null +++ b/k8s/back-2-trade-chart/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "back2trade.serviceAccountName" . }} + labels: + {{- include "back2trade.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount | default false }} +{{- end }} \ No newline at end of file diff --git a/k8s/back-2-trade-chart/values.yaml b/k8s/back-2-trade-chart/values.yaml new file mode 100644 index 0000000..d27477c --- /dev/null +++ b/k8s/back-2-trade-chart/values.yaml @@ -0,0 +1,175 @@ +replicaCount: 2 + +backend: + enabled: true + image: + repository: YOUR_REGISTRY/back2trade-backend + tag: latest + pullPolicy: Always + service: + type: ClusterIP + port: 3001 + env: + NODE_ENV: production + PORT: "3001" + DATABASE_URL: "postgresql://back2trade:password@back2trade-database:5432/back2trade?schema=public" + PRISMA_SEED: "false" + +frontend: + enabled: true + image: + repository: YOUR_REGISTRY/back2trade-frontend + tag: latest + pullPolicy: Always + service: + type: ClusterIP + port: 3000 + env: + NODE_ENV: development + PORT: "3000" + NEXT_PUBLIC_API_URL: "http://back2trade-backend:3001/api" + +# Database configuration (PostgreSQL for Prisma) +database: + enabled: true + image: + repository: postgres + tag: "15-alpine" + pullPolicy: IfNotPresent + service: + type: ClusterIP + port: 5432 + env: + POSTGRES_DB: "back2trade" + POSTGRES_USER: "back2trade" + POSTGRES_PASSWORD: "password" + persistence: + enabled: true + size: 1Gi + storageClass: "" + +# Prisma configuration +prisma: + migrations: + enabled: true + # Run seed on first deployment + seed: false + # Database migration job settings + migrationJob: + enabled: false + image: + repository: YOUR_REGISTRY/back2trade-backend + tag: latest + pullPolicy: Always + +# Simple auto-update configuration +autoUpdate: + enabled: true + # Force pod restart annotation to trigger updates + restartPolicy: "Always" + +# Ingress configuration +ingress: + enabled: false + className: "" + annotations: {} + hosts: + - host: back2trade.local + paths: + - path: / + pathType: Prefix + service: frontend + - path: /api + pathType: Prefix + service: backend + tls: [] + +# Secrets for external APIs +secrets: + finnhub: + token: "" + api: "https://finnhub.io/api/v1" + database: + url: "postgresql://back2trade:password@back2trade-database:5432/back2trade?schema=public" + +# Service account +serviceAccount: + create: true + annotations: {} + name: "" + +# Pod security context +podSecurityContext: {} + +# Security context +securityContext: {} + +# Resources +resources: + backend: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 250m + memory: 256Mi + frontend: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 250m + memory: 256Mi + database: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 250m + memory: 256Mi + +# Node selector +nodeSelector: {} + +# Tolerations +tolerations: [] + +# Affinity +affinity: {} + +# Health checks +healthChecks: + backend: + livenessProbe: + httpGet: + path: / + port: 3001 + initialDelaySeconds: 60 + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: 3001 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + frontend: + livenessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: 3000 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 \ No newline at end of file diff --git a/k8s/backend/Dockerfile b/k8s/backend/Dockerfile new file mode 100644 index 0000000..ec58610 --- /dev/null +++ b/k8s/backend/Dockerfile @@ -0,0 +1,89 @@ +# Multi-stage optimized Dockerfile for NestJS backend +FROM node:20-alpine AS dependencies + +# Install security updates and required packages +RUN apk update && apk upgrade && \ + apk add --no-cache openssl dumb-init && \ + rm -rf /var/cache/apk/* + +WORKDIR /app + +# Copy package files first for better Docker layer caching +COPY back2trade/api/app_api/package*.json ./ + +# Install only production dependencies in a separate stage +RUN npm ci --only=production --no-audit --no-fund && \ + npm cache clean --force + +# Build stage +FROM node:20-alpine AS build + +RUN apk add --no-cache openssl + +WORKDIR /app + +# Copy package files +COPY back2trade/api/app_api/package*.json ./ + +# Install all dependencies (including dev dependencies for build) +RUN npm ci --no-audit --no-fund + +# Copy source code and configuration +COPY back2trade/api/app_api/src ./src +COPY back2trade/api/app_api/prisma ./prisma +COPY back2trade/api/app_api/tsconfig*.json ./ +COPY back2trade/api/app_api/nest-cli.json ./ + +# Generate Prisma client +RUN npx prisma generate + +# Build the application +RUN npm run build && \ + npm prune --production + +# Production stage +FROM node:20-alpine AS production + +# Create non-root user for security +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nestjs -u 1001 + +# Install runtime dependencies and security updates +RUN apk update && apk upgrade && \ + apk add --no-cache openssl dumb-init curl && \ + rm -rf /var/cache/apk/* + +WORKDIR /app + +# Copy production dependencies from dependencies stage +COPY --from=dependencies --chown=nestjs:nodejs /app/node_modules ./node_modules + +# Copy built application from build stage +COPY --from=build --chown=nestjs:nodejs /app/dist ./dist +COPY --from=build --chown=nestjs:nodejs /app/prisma ./prisma +COPY --from=build --chown=nestjs:nodejs /app/package.json ./package.json + +# Copy generated Prisma client from build stage +COPY --from=build --chown=nestjs:nodejs /app/node_modules/.prisma ./node_modules/.prisma +COPY --from=build --chown=nestjs:nodejs /app/node_modules/@prisma ./node_modules/@prisma + +# Set environment variables +ENV NODE_ENV=production \ + PORT=3001 \ + NODE_OPTIONS="--max-old-space-size=256" + +# Switch to non-root user +USER nestjs + +# Expose port +EXPOSE 3001 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:3001/ || exit 1 + +# Use dumb-init to handle signals properly +ENTRYPOINT ["dumb-init", "--"] + +# Run the application - NestJS builds main.ts to dist/src/main.js +CMD ["node", "dist/src/main.js"] diff --git a/k8s/docker-build.yml b/k8s/docker-build.yml new file mode 100644 index 0000000..a3c1a6e --- /dev/null +++ b/k8s/docker-build.yml @@ -0,0 +1,54 @@ +version: '3.8' + +services: + backend: + build: + context: . + dockerfile: k8s/backend/Dockerfile + target: production + args: + - NODE_ENV=production + cache_from: + - node:20-alpine + - ${DOCKER_USERNAME}/back2trade-backend:latest + image: ${DOCKER_USERNAME}/back2trade-backend:${IMAGE_TAG:-latest} + platform: linux/amd64 + networks: + - back2trade-network + + frontend: + build: + context: . + dockerfile: k8s/frontend/Dockerfile + target: production + args: + - NODE_ENV=production + - NEXT_TELEMETRY_DISABLED=1 + cache_from: + - node:20-alpine + - ${DOCKER_USERNAME}/back2trade-frontend:latest + image: ${DOCKER_USERNAME}/back2trade-frontend:${IMAGE_TAG:-latest} + platform: linux/amd64 + networks: + - back2trade-network + +networks: + back2trade-network: + driver: bridge + +# Example usage: +# 1. Set environment variables: +# export DOCKER_USERNAME=yourusername +# export IMAGE_TAG=v1.0.0 +# +# 2. Build images: +# docker-compose -f docker-build.yml build +# +# 3. Push to Docker Hub: +# docker-compose -f docker-build.yml push +# +# 4. Build and push in one command: +# docker-compose -f docker-build.yml build --push +# +# 5. Build with specific tag: +# IMAGE_TAG=v1.2.3 docker-compose -f docker-build.yml build --push \ No newline at end of file diff --git a/k8s/frontend/Dockerfile b/k8s/frontend/Dockerfile new file mode 100644 index 0000000..3f89a17 --- /dev/null +++ b/k8s/frontend/Dockerfile @@ -0,0 +1,43 @@ +# Simple frontend Dockerfile for Next.js +FROM node:20-alpine + +# Install dumb-init for proper signal handling +RUN apk add --no-cache dumb-init + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nextjs -u 1001 + +WORKDIR /app + +# Copy package files +COPY back2trade/web2/back2trade/package*.json ./ + +# Install dependencies +RUN npm install + +# Copy source code +COPY back2trade/web2/back2trade/ . + +# Change ownership to nextjs user +RUN chown -R nextjs:nodejs /app + +# Switch to non-root user +USER nextjs + +# Set environment variables +ENV PORT=3000 +ENV NEXT_TELEMETRY_DISABLED=1 + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:3000/ || exit 1 + +# Use dumb-init +ENTRYPOINT ["dumb-init", "--"] + +# Run development server (NODE_ENV from Kubernetes will determine mode) +CMD ["npm", "run", "dev"]