Add full-stack Hello World app with CI/CD

- Go backend with PostgreSQL connection
- Next.js frontend with TypeScript
- Docker Compose for local development
- Woodpecker CI pipeline for build and push
- Kubernetes manifests with Kustomize
- ArgoCD application for GitOps deployment

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Erdenebat Tsenddorj
2026-01-21 21:06:08 +08:00
parent 2c4e7d2c38
commit f311439621
26 changed files with 955 additions and 1 deletions

View File

@@ -0,0 +1,64 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: hell-world
labels:
app: backend
spec:
replicas: 2
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: registry.gecore.mn/library/hell-world-backend:latest
ports:
- containerPort: 8080
name: http
env:
- name: DB_HOST
value: postgres
- name: DB_PORT
value: "5432"
- name: DB_USER
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_PASSWORD
- name: DB_NAME
valueFrom:
secretKeyRef:
name: postgres-secret
key: POSTGRES_DB
- name: PORT
value: "8080"
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
livenessProbe:
httpGet:
path: /api/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: hell-world
labels:
app: backend
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: http
selector:
app: backend

View File

@@ -0,0 +1,45 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: hell-world
labels:
app: frontend
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: registry.gecore.mn/library/hell-world-frontend:latest
ports:
- containerPort: 3000
name: http
env:
- name: NEXT_PUBLIC_API_URL
value: "https://hell-world-api.gecore.mn"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 5

View File

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

38
manifests/ingress.yaml Normal file
View File

@@ -0,0 +1,38 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hell-world
namespace: hell-world
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- hell-world.gecore.mn
- hell-world-api.gecore.mn
secretName: hell-world-tls
rules:
# Frontend
- host: hell-world.gecore.mn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 3000
# Backend API
- host: hell-world-api.gecore.mn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend
port:
number: 8080

View File

@@ -0,0 +1,20 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: hell-world
resources:
- namespace.yaml
- postgres-secret.yaml
- postgres-pvc.yaml
- postgres-deployment.yaml
- postgres-service.yaml
- backend-deployment.yaml
- backend-service.yaml
- frontend-deployment.yaml
- frontend-service.yaml
- ingress.yaml
commonLabels:
app.kubernetes.io/name: hell-world
app.kubernetes.io/managed-by: kustomize

6
manifests/namespace.yaml Normal file
View File

@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: hell-world
labels:
app: hell-world

View File

@@ -0,0 +1,56 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: hell-world
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
name: postgres
envFrom:
- secretRef:
name: postgres-secret
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
livenessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: hell-world
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-path

View File

@@ -0,0 +1,10 @@
apiVersion: v1
kind: Secret
metadata:
name: postgres-secret
namespace: hell-world
type: Opaque
stringData:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: hellodb

View File

@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: hell-world
labels:
app: postgres
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
protocol: TCP
name: postgres
selector:
app: postgres