Skip to content

Commit be0a01e

Browse files
committed
feat(infra): add Helm chart for GistPin application
1 parent 1309203 commit be0a01e

224 files changed

Lines changed: 92161 additions & 13 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

32 Bytes
Binary file not shown.

infrastructure/k8s/Chart.yaml

Whitespace-only changes.

infrastructure/k8s/NOTES.txt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
🎉 GistPin {{ .Chart.AppVersion }} deployed successfully!
2+
3+
Release : {{ .Release.Name }}
4+
Namespace: {{ .Release.Namespace }}
5+
6+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
7+
8+
{{- if .Values.backend.enabled }}
9+
10+
Backend API
11+
───────────
12+
{{- if .Values.ingress.enabled }}
13+
External URL: https://{{ (index .Values.ingress.hosts 0).host }}
14+
{{- else }}
15+
Port-forward to reach the API locally:
16+
17+
kubectl port-forward svc/{{ include "gistpin.fullname" . }}-backend 3000:80 -n {{ .Release.Namespace }}
18+
19+
Then: http://localhost:3000
20+
Swagger: http://localhost:3000/api/docs
21+
{{- end }}
22+
{{- end }}
23+
24+
{{- if .Values.analytics.enabled }}
25+
26+
Analytics Dashboard
27+
────────────────────
28+
{{- if .Values.ingress.enabled }}
29+
External URL: https://{{ (index .Values.ingress.hosts 1).host }}
30+
{{- else }}
31+
Port-forward:
32+
33+
kubectl port-forward svc/{{ include "gistpin.fullname" . }}-analytics 3001:80 -n {{ .Release.Namespace }}
34+
35+
Then: http://localhost:3001
36+
{{- end }}
37+
{{- end }}
38+
39+
{{- if .Values.postgresql.enabled }}
40+
41+
PostgreSQL (PostGIS)
42+
────────────────────
43+
Internal host : {{ include "gistpin.postgresHost" . }}:5432
44+
Database : {{ .Values.postgresql.auth.database }}
45+
Username : {{ .Values.postgresql.auth.username }}
46+
47+
⚠ Change postgresql.auth.password and backend.secrets.DATABASE_PASSWORD
48+
in your values override before going to production.
49+
{{- end }}
50+
51+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
52+
53+
Useful commands:
54+
55+
# Check pod status
56+
kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }}
57+
58+
# Stream backend logs
59+
kubectl logs -n {{ .Release.Namespace }} -l app.kubernetes.io/component=backend -f
60+
61+
# Upgrade with new image tag
62+
helm upgrade {{ .Release.Name }} ./gistpin --set backend.image.tag=sha-<commit>
63+
64+
# Rollback
65+
helm rollback {{ .Release.Name }} -n {{ .Release.Namespace }}

infrastructure/k8s/README.md

Lines changed: 139 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,146 @@
1-
# Kubernetes Manifests
1+
# GistPin Helm Chart
22

3-
Kubernetes deployment configurations for GistPin services.
3+
Packages the full GistPin stack — NestJS backend API, Next.js analytics dashboard, and a PostGIS-enabled PostgreSQL database — into a single installable Helm chart.
44

5-
## Overview
5+
## Chart structure
66

7-
Contains Kubernetes resource definitions for deploying GistPin to production clusters.
7+
```
8+
infrastructure/k8s/helm/gistpin/
9+
├── Chart.yaml # Chart metadata and dependencies
10+
├── values.yaml # Default values (all overridable)
11+
├── README.md # This file
12+
└── templates/
13+
├── _helpers.tpl # Shared template helpers
14+
├── NOTES.txt # Post-install instructions
15+
├── serviceaccount.yaml
16+
├── backend-configmap.yaml # Non-sensitive env vars
17+
├── backend-secret.yaml # Sensitive env vars (created when existingSecret is unset)
18+
├── backend-deployment.yaml
19+
├── backend-service.yaml
20+
├── backend-hpa.yaml
21+
├── analytics-deployment.yaml
22+
├── analytics-service.yaml
23+
├── analytics-hpa.yaml
24+
└── ingress.yaml
25+
```
826

9-
## Contents
27+
## Prerequisites
1028

11-
- Deployment manifests
12-
- Service definitions
13-
- ConfigMaps and Secrets
14-
- Ingress configurations
15-
- Helm charts
29+
- Kubernetes 1.25+
30+
- Helm 3.10+
31+
- (Optional) [helm-secrets](https://github.com/jkroepke/helm-secrets) for encrypted values
1632

17-
## Usage
33+
## Quick start
1834

19-
Apply manifests using kubectl or Helm.
35+
```bash
36+
# 1. Add the Bitnami repo (required for PostgreSQL sub-chart)
37+
helm repo add bitnami https://charts.bitnami.com/bitnami
38+
helm repo update
39+
40+
# 2. Fetch sub-chart dependencies
41+
helm dependency update ./infrastructure/k8s/helm/gistpin
42+
43+
# 3. Install to the gistpin namespace
44+
helm upgrade --install gistpin ./infrastructure/k8s/helm/gistpin \
45+
--namespace gistpin --create-namespace \
46+
--set backend.image.tag=sha-<commit>
47+
```
48+
49+
## Configuration
50+
51+
All values are documented inline in `values.yaml`. The most commonly overridden keys are:
52+
53+
| Key | Default | Description |
54+
|-----|---------|-------------|
55+
| `backend.image.tag` | `.Chart.AppVersion` | Docker image tag — set to the CI-produced SHA tag |
56+
| `backend.replicaCount` | `2` | Number of backend pods |
57+
| `backend.env.SOROBAN_RPC_URL` | testnet URL | Soroban RPC endpoint |
58+
| `backend.env.CONTRACT_ID_GIST_REGISTRY` | `""` | Deployed contract ID |
59+
| `backend.env.CORS_ORIGINS` | `""` | Comma-separated allowed origins |
60+
| `backend.secrets.DATABASE_PASSWORD` | `changeme` | **Override in production** |
61+
| `backend.secrets.STELLAR_SECRET_KEY` | `""` | Backend signing keypair |
62+
| `backend.existingSecret` | `""` | Use a pre-existing Secret instead |
63+
| `analytics.image.tag` | `.Chart.AppVersion` | Analytics image tag |
64+
| `analytics.env.NEXT_PUBLIC_API_URL` | `""` | Backend URL exposed to the browser |
65+
| `ingress.enabled` | `false` | Enable Ingress resource |
66+
| `ingress.hosts` | see values | Host rules for backend and analytics |
67+
| `postgresql.enabled` | `true` | Deploy bundled PostgreSQL (PostGIS) |
68+
| `postgresql.auth.password` | `changeme` | **Override in production** |
69+
70+
## Environment-specific overrides
71+
72+
Create a thin override file per environment:
73+
74+
```yaml
75+
# values.prod.yaml
76+
backend:
77+
replicaCount: 3
78+
image:
79+
tag: sha-a1b2c3d
80+
env:
81+
NODE_ENV: production
82+
SOROBAN_RPC_URL: https://soroban-mainnet.stellar.org
83+
STELLAR_NETWORK_PASSPHRASE: "Public Global Stellar Network ; September 2015"
84+
CONTRACT_ID_GIST_REGISTRY: "CXXXX..."
85+
CORS_ORIGINS: "https://app.gistpin.io"
86+
existingSecret: gistpin-backend-secret # managed by external-secrets
87+
88+
analytics:
89+
replicaCount: 2
90+
env:
91+
NEXT_PUBLIC_API_URL: https://api.gistpin.io
92+
93+
ingress:
94+
enabled: true
95+
className: nginx
96+
annotations:
97+
cert-manager.io/cluster-issuer: letsencrypt-prod
98+
tls:
99+
- secretName: gistpin-tls
100+
hosts:
101+
- api.gistpin.io
102+
- analytics.gistpin.io
103+
104+
postgresql:
105+
enabled: false # use a managed database in production
106+
```
107+
108+
Then install with:
109+
110+
```bash
111+
helm upgrade --install gistpin ./infrastructure/k8s/helm/gistpin \
112+
--namespace gistpin --create-namespace \
113+
-f values.prod.yaml
114+
```
115+
116+
## Managing secrets
117+
118+
For production, avoid putting plaintext passwords in values files. Options:
119+
120+
1. **`existingSecret`** — pre-create the Secret via CI/external-secrets and point `backend.existingSecret` at it. The chart skips creating its own Secret.
121+
2. **[helm-secrets](https://github.com/jkroepke/helm-secrets)** — encrypt a `values.secrets.yaml` with SOPS and pass it with `-f`.
122+
3. **External Secrets Operator** — sync secrets from AWS Secrets Manager / Vault into the cluster.
123+
124+
## Upgrading the chart
125+
126+
```bash
127+
# Bump chart version in Chart.yaml, then:
128+
helm upgrade gistpin ./infrastructure/k8s/helm/gistpin \
129+
--namespace gistpin \
130+
--set backend.image.tag=sha-<new-commit>
131+
```
132+
133+
## Rollback
134+
135+
```bash
136+
helm history gistpin -n gistpin
137+
helm rollback gistpin <revision> -n gistpin
138+
```
139+
140+
## Uninstall
141+
142+
```bash
143+
helm uninstall gistpin -n gistpin
144+
# PVCs are NOT deleted automatically; remove manually if needed:
145+
kubectl delete pvc -n gistpin -l app.kubernetes.io/instance=gistpin
146+
```
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{{- if .Values.analytics.enabled -}}
2+
apiVersion: apps/v1
3+
kind: Deployment
4+
metadata:
5+
name: {{ include "gistpin.fullname" . }}-analytics
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
{{- include "gistpin.labels" . | nindent 4 }}
9+
app.kubernetes.io/component: analytics
10+
{{- with .Values.analytics.podAnnotations }}
11+
annotations:
12+
{{- toYaml . | nindent 4 }}
13+
{{- end }}
14+
spec:
15+
{{- if not .Values.analytics.autoscaling.enabled }}
16+
replicas: {{ .Values.analytics.replicaCount }}
17+
{{- end }}
18+
selector:
19+
matchLabels:
20+
{{- include "gistpin.componentSelectorLabels" (dict "component" "analytics" "root" .) | nindent 6 }}
21+
template:
22+
metadata:
23+
labels:
24+
{{- include "gistpin.componentSelectorLabels" (dict "component" "analytics" "root" .) | nindent 8 }}
25+
{{- with .Values.analytics.podAnnotations }}
26+
annotations:
27+
{{- toYaml . | nindent 8 }}
28+
{{- end }}
29+
spec:
30+
{{- with (default .Values.global.imagePullSecrets list) }}
31+
imagePullSecrets:
32+
{{- toYaml . | nindent 8 }}
33+
{{- end }}
34+
serviceAccountName: {{ include "gistpin.serviceAccountName" . }}
35+
containers:
36+
- name: analytics
37+
image: "{{ .Values.analytics.image.repository }}:{{ .Values.analytics.image.tag | default .Chart.AppVersion }}"
38+
imagePullPolicy: {{ .Values.analytics.image.pullPolicy }}
39+
ports:
40+
- name: http
41+
containerPort: {{ .Values.analytics.service.targetPort }}
42+
protocol: TCP
43+
env:
44+
- name: NEXT_PUBLIC_API_URL
45+
value: {{ .Values.analytics.env.NEXT_PUBLIC_API_URL | quote }}
46+
livenessProbe:
47+
{{- toYaml .Values.analytics.livenessProbe | nindent 12 }}
48+
readinessProbe:
49+
{{- toYaml .Values.analytics.readinessProbe | nindent 12 }}
50+
resources:
51+
{{- toYaml .Values.analytics.resources | nindent 12 }}
52+
{{- with .Values.analytics.nodeSelector }}
53+
nodeSelector:
54+
{{- toYaml . | nindent 8 }}
55+
{{- end }}
56+
{{- with .Values.analytics.affinity }}
57+
affinity:
58+
{{- toYaml . | nindent 8 }}
59+
{{- end }}
60+
{{- with .Values.analytics.tolerations }}
61+
tolerations:
62+
{{- toYaml . | nindent 8 }}
63+
{{- end }}
64+
{{- end }}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{{- if and .Values.analytics.enabled .Values.analytics.autoscaling.enabled -}}
2+
apiVersion: autoscaling/v2
3+
kind: HorizontalPodAutoscaler
4+
metadata:
5+
name: {{ include "gistpin.fullname" . }}-analytics
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
{{- include "gistpin.labels" . | nindent 4 }}
9+
app.kubernetes.io/component: analytics
10+
spec:
11+
scaleTargetRef:
12+
apiVersion: apps/v1
13+
kind: Deployment
14+
name: {{ include "gistpin.fullname" . }}-analytics
15+
minReplicas: {{ .Values.analytics.autoscaling.minReplicas }}
16+
maxReplicas: {{ .Values.analytics.autoscaling.maxReplicas }}
17+
metrics:
18+
- type: Resource
19+
resource:
20+
name: cpu
21+
target:
22+
type: Utilization
23+
averageUtilization: {{ .Values.analytics.autoscaling.targetCPUUtilizationPercentage }}
24+
{{- end }}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{{- if .Values.analytics.enabled -}}
2+
apiVersion: v1
3+
kind: Service
4+
metadata:
5+
name: {{ include "gistpin.fullname" . }}-analytics
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
{{- include "gistpin.labels" . | nindent 4 }}
9+
app.kubernetes.io/component: analytics
10+
spec:
11+
type: {{ .Values.analytics.service.type }}
12+
ports:
13+
- port: {{ .Values.analytics.service.port }}
14+
targetPort: {{ .Values.analytics.service.targetPort }}
15+
protocol: TCP
16+
name: http
17+
selector:
18+
{{- include "gistpin.componentSelectorLabels" (dict "component" "analytics" "root" .) | nindent 4 }}
19+
{{- end }}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{{- if .Values.backend.enabled -}}
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: {{ include "gistpin.fullname" . }}-backend-config
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
{{- include "gistpin.labels" . | nindent 4 }}
9+
app.kubernetes.io/component: backend
10+
data:
11+
NODE_ENV: {{ .Values.backend.env.NODE_ENV | quote }}
12+
PORT: {{ .Values.backend.env.PORT | quote }}
13+
DATABASE_HOST: {{ include "gistpin.postgresHost" . | quote }}
14+
DATABASE_PORT: "5432"
15+
DATABASE_USER: {{ .Values.postgresql.auth.username | quote }}
16+
DATABASE_NAME: {{ .Values.postgresql.auth.database | quote }}
17+
SOROBAN_RPC_URL: {{ .Values.backend.env.SOROBAN_RPC_URL | quote }}
18+
STELLAR_NETWORK_PASSPHRASE: {{ .Values.backend.env.STELLAR_NETWORK_PASSPHRASE | quote }}
19+
CONTRACT_ID_GIST_REGISTRY: {{ .Values.backend.env.CONTRACT_ID_GIST_REGISTRY | quote }}
20+
CORS_ORIGINS: {{ .Values.backend.env.CORS_ORIGINS | quote }}
21+
{{- end }}

0 commit comments

Comments
 (0)