Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,24 @@ RUN npm run build
# Stage 2: Production
FROM node:18-alpine AS production

# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init \
&& rm -rf /var/cache/apk/*

ENV NODE_ENV=production
WORKDIR /app

# Install production dependencies as root before switching user
RUN apk add --no-cache dumb-init curl

COPY package.json package-lock.json ./
RUN npm ci --only=production --ignore-scripts \
&& npm cache clean --force
RUN npm ci --omit=dev --ignore-scripts

COPY --from=builder /app/dist ./dist

# Create writable tmp dir for the app, then lock down ownership
RUN mkdir -p /app/tmp && chown -R node:node /app

# Drop to non-root user
USER node

EXPOSE 3000

# no-new-privileges enforced at runtime via security_opt in compose;
# dumb-init ensures SIGTERM is forwarded to the Node process
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1

ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/main.js"]
CMD ["node", "dist/main.js"]
8 changes: 8 additions & 0 deletions charts/teachlink-backend/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.tgz
*.bak
*.swp
.DS_Store
.git
.gitignore
node_modules
README.md
6 changes: 6 additions & 0 deletions charts/teachlink-backend/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v2
name: teachlink-backend
description: A Helm chart for deploying the TeachLink backend service
type: application
version: 0.1.0
appVersion: "0.0.1"
30 changes: 30 additions & 0 deletions charts/teachlink-backend/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{{- define "teachlink-backend.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{- define "teachlink-backend.fullname" -}}
{{- if .Values.fullnameOverride -}}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" (include "teachlink-backend.name" .) .Release.Name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}

{{- define "teachlink-backend.labels" -}}
helm.sh/chart: {{ include "teachlink-backend.chart" . }}
app.kubernetes.io/name: {{ include "teachlink-backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end -}}

{{- define "teachlink-backend.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version -}}
{{- end -}}

{{- define "teachlink-backend.env" -}}
{{- range $index, $env := .Values.env }}
- name: {{ $env.name }}
value: {{ $env.value | quote }}
{{- end -}}
{{- end -}}
26 changes: 26 additions & 0 deletions charts/teachlink-backend/templates/configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "teachlink-backend.fullname" . }}-config
namespace: {{ .Release.Namespace }}
labels:
{{- include "teachlink-backend.labels" . | nindent 4 }}
data:
NODE_ENV: {{ .Values.config.nodeEnv | quote }}
PORT: "3000"
DATABASE_HOST: {{ .Values.config.databaseHost | quote }}
DATABASE_PORT: {{ .Values.config.databasePort | quote }}
DATABASE_NAME: {{ .Values.config.databaseName | quote }}
DATABASE_POOL_MAX: {{ .Values.config.databasePoolMax | quote }}
DATABASE_POOL_MIN: {{ .Values.config.databasePoolMin | quote }}
REDIS_HOST: {{ .Values.config.redisHost | quote }}
REDIS_PORT: {{ .Values.config.redisPort | quote }}
ELASTICSEARCH_NODE: {{ .Values.config.elasticsearchNode | quote }}
JWT_EXPIRES_IN: {{ .Values.config.jwtExpiresIn | quote }}
JWT_REFRESH_EXPIRES_IN: {{ .Values.config.jwtRefreshExpiresIn | quote }}
BCRYPT_ROUNDS: {{ .Values.config.bcryptRounds | quote }}
APP_URL: {{ .Values.config.appUrl | quote }}
CORS_ALLOWED_ORIGINS: {{ .Values.config.corsAllowedOrigins | quote }}
TRUST_PROXY: {{ .Values.config.trustProxy | quote }}
API_DEFAULT_VERSION: {{ .Values.config.apiDefaultVersion | quote }}
API_SUPPORTED_VERSIONS: {{ .Values.config.apiSupportedVersions | quote }}
77 changes: 77 additions & 0 deletions charts/teachlink-backend/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "teachlink-backend.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "teachlink-backend.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "teachlink-backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "teachlink-backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
containers:
- name: {{ include "teachlink-backend.name" . }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
ports:
- name: http
containerPort: 3000
envFrom:
- configMapRef:
name: {{ include "teachlink-backend.fullname" . }}-config
- secretRef:
name: {{ include "teachlink-backend.fullname" . }}-secret
resources:
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 3
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
42 changes: 42 additions & 0 deletions charts/teachlink-backend/templates/hpa.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{{- if .Values.autoscaling.enabled -}}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "teachlink-backend.fullname" . }}-hpa
namespace: {{ .Release.Namespace }}
labels:
{{- include "teachlink-backend.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "teachlink-backend.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 2
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 1
periodSeconds: 120
{{- end -}}
28 changes: 28 additions & 0 deletions charts/teachlink-backend/templates/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "teachlink-backend.fullname" . }}
labels:
{{- include "teachlink-backend.labels" . | nindent 4 }}
annotations:
{{- toYaml .Values.ingress.annotations | nindent 4 }}
spec:
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "teachlink-backend.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
tls:
{{- toYaml .Values.ingress.tls | nindent 4 }}
{{- end -}}
22 changes: 22 additions & 0 deletions charts/teachlink-backend/templates/secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ include "teachlink-backend.fullname" . }}-secret
namespace: {{ .Release.Namespace }}
labels:
{{- include "teachlink-backend.labels" . | nindent 4 }}
type: Opaque
stringData:
DATABASE_USER: {{ .Values.secrets.databaseUser | quote }}
DATABASE_PASSWORD: {{ .Values.secrets.databasePassword | quote }}
JWT_SECRET: {{ .Values.secrets.jwtSecret | quote }}
JWT_REFRESH_SECRET: {{ .Values.secrets.jwtRefreshSecret | quote }}
ENCRYPTION_SECRET: {{ .Values.secrets.encryptionSecret | quote }}
SESSION_SECRET: {{ .Values.secrets.sessionSecret | quote }}
STRIPE_SECRET_KEY: {{ .Values.secrets.stripeSecretKey | quote }}
STRIPE_WEBHOOK_SECRET: {{ .Values.secrets.stripeWebhookSecret | quote }}
AWS_ACCESS_KEY_ID: {{ .Values.secrets.awsAccessKeyId | quote }}
AWS_SECRET_ACCESS_KEY: {{ .Values.secrets.awsSecretAccessKey | quote }}
AWS_REGION: {{ .Values.secrets.awsRegion | quote }}
AWS_S3_BUCKET_NAME: {{ .Values.secrets.awsS3BucketName | quote }}
SENDGRID_API_KEY: {{ .Values.secrets.sendgridApiKey | quote }}
17 changes: 17 additions & 0 deletions charts/teachlink-backend/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "teachlink-backend.fullname" . }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "teachlink-backend.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
selector:
app.kubernetes.io/name: {{ include "teachlink-backend.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: 3000
protocol: TCP
82 changes: 82 additions & 0 deletions charts/teachlink-backend/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
replicaCount: 2

image:
repository: teachlink-backend
tag: latest
pullPolicy: IfNotPresent

service:
type: ClusterIP
port: 80

ingress:
enabled: false
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
hosts:
- host: api.teachlink.io
paths:
- path: /
pathType: Prefix
tls:
- secretName: teachlink-tls
hosts:
- api.teachlink.io

resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi

autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 60
targetMemoryUtilizationPercentage: 70

# Non-sensitive configuration (goes into ConfigMap)
config:
nodeEnv: "production"
databaseHost: "postgres-service"
databasePort: "5432"
databaseName: "teachlink"
databasePoolMax: "30"
databasePoolMin: "5"
redisHost: "redis-service"
redisPort: "6379"
elasticsearchNode: "http://elasticsearch-service:9200"
jwtExpiresIn: "15m"
jwtRefreshExpiresIn: "7d"
bcryptRounds: "12"
appUrl: "https://api.teachlink.io"
corsAllowedOrigins: "https://teachlink.io"
trustProxy: "true"
apiDefaultVersion: "1"
apiSupportedVersions: "1"

# Sensitive configuration (goes into Secret)
# Override these with --set or a separate values file; never commit real values.
secrets:
databaseUser: "postgres"
databasePassword: "REPLACE_ME"
jwtSecret: "REPLACE_ME"
jwtRefreshSecret: "REPLACE_ME"
encryptionSecret: "REPLACE_ME"
sessionSecret: "REPLACE_ME"
stripeSecretKey: "REPLACE_ME"
stripeWebhookSecret: "REPLACE_ME"
awsAccessKeyId: "REPLACE_ME"
awsSecretAccessKey: "REPLACE_ME"
awsRegion: "us-east-1"
awsS3BucketName: "REPLACE_ME"
sendgridApiKey: "REPLACE_ME"

nodeSelector: {}
tolerations: []
affinity: {}
podAnnotations: {}
Loading