A production-ready 3-tier application deployment using Kubernetes, ArgoCD for GitOps, and PostgreSQL managed by Crunchy Data Operator.
- Architecture Overview
- Prerequisites
- Quick Start
- Detailed Setup
- HTTPS Configuration
- Deployment Workflow
- Troubleshooting
- Cleanup
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ (https://skypoint.local) │
└────────────────────────┬────────────────────────────────────┘
│ HTTPS
│
┌────▼─────┐
│ Ingress │
│ (nginx) │
└────┬─────┘
│
┌────────────┴────────────┐
│ │
┌────▼─────┐ ┌─────▼────┐
│ Frontend │ │ Backend │
│ Service │ │ Service │
│ (Port 80)│ │(Port 5000)│
└────┬─────┘ └─────┬────┘
│ │
┌────▼─────┐ ┌─────▼────┐
│ Frontend │ │ Backend │
│ Pods │ │ Pods │
│(3 replicas)│ │(3 replicas)│
└──────────┘ └─────┬────┘
│
┌──────▼──────┐
│ PostgreSQL │
│ Cluster │
└─────────────┘
┌─────────────┐ Git Push ┌──────────────┐
│ Developer │ ───────────────> │ GitHub Repo │
└─────────────┘ └──────┬───────┘
│ Monitors
│
┌────▼──────┐
│ ArgoCD │
│ Server │
└────┬──────┘
│ Applies
│
┌──────▼────────┐
│ Kubernetes │
│ Cluster │
└───────────────┘
- Minikube or any Kubernetes cluster
- kubectl CLI
- Docker and Docker Hub account
- Git
- ArgoCD CLI (optional)
- mkcert (for HTTPS)
# 1. Start Minikube
minikube start
# 2. Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# 3. Install PostgreSQL Operator
kubectl apply -k kustomize/install/namespace
kubectl apply --server-side -k kustomize/install/default
# 4. Access ArgoCD UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
# 5. Get ArgoCD admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
# 6. Create ArgoCD application
kubectl apply -f argocd-app.yaml
# 7. Enable Ingress
minikube addons enable ingress
# 8. Start Minikube tunnel (keep running in separate terminal)
minikube tunnelBuild and push both frontend and backend images to Docker Hub:
# Backend
cd backend
docker build -t <your-dockerhub-username>/backend:1.0 .
docker push <your-dockerhub-username>/backend:1.0
# Frontend
cd ../frontend
docker build -t <your-dockerhub-username>/frontend:1.0 .
docker push <your-dockerhub-username>/frontend:1.0The repository uses Kustomize to organize Kubernetes manifests:
.
├── kustomization.yaml # Root kustomization
├── namespace.yaml
├── configmap.yaml
├── secrets.yaml
├── ingress.yaml
├── backend/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ └── service.yaml
├── frontend/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
└── db/
├── kustomization.yaml
└── postgres-cluster.yaml
Why Kustomize? ArgoCD couldn't detect YAML files in subdirectories when using a flat structure. Kustomize allows recursive resource discovery and proper organization.
Add your repository to ArgoCD:
argocd repo add https://github.com/<your-username>/3Tier-deployment-with-ArgoCd \
--username <github-username> \
--password <github-pat-token> \
--insecure-skip-server-verificationCreate the ArgoCD application (see argocd-app.yaml):
- Automatic sync enabled
- Self-healing enabled
- Prune resources enabled
The PostgreSQL cluster is managed by Crunchy Data Operator:
Installation:
git clone --depth 1 "https://github.com/CrunchyData/postgres-operator-examples.git"
cd postgres-operator-examples
kubectl apply -k kustomize/install/namespace
kubectl apply --server-side -k kustomize/install/defaultDatabase Credentials: After the PostgreSQL cluster is created, credentials are stored in a secret:
kubectl get secret skypoint-db-pguser-skypoint -n skypoint -o yamlThe secret contains:
user: Database usernamepassword: Database passworddbname: Database namehost: Database hostport: Database port
The backend requires two resources:
ConfigMap (configmap.yaml):
NODE_ENV: Environment (dev/prod)DEBUG: Debug modeDB_NAME: Database nameDB_HOST: Database service hostname
Secrets (secrets.yaml) - Create manually:
DB_USERNAMEDB_PASSWORDSENDGRID_API_KEYSTRIPE_SECRET_KEYSTRIPE_PUBLISHABLE_KEY
Deployment features:
- 3 replicas for high availability
- Rolling update strategy
- Health checks (readiness & liveness probes)
- Environment variables from ConfigMap and Secrets
ConfigMap for runtime configuration:
The frontend uses a ConfigMap to inject environment variables at runtime via env.js.
Deployment features:
- 3 replicas
- Volume mount for configuration
- Health checks on root path
Enable Ingress:
minikube addons enable ingressUpdate /etc/hosts:
sudo nano /etc/hostsAdd:
127.0.0.1 skypoint.local
127.0.0.1 api.skypoint.local
Routes:
skypoint.local→ Frontend Service (Port 80)api.skypoint.local→ Backend Service (Port 5000)
Stripe payment integration requires HTTPS connections for security. We use mkcert to create locally-trusted certificates.
1. Install mkcert:
sudo apt install libnss3-tools
wget https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64
chmod +x mkcert-v1.4.4-linux-amd64
sudo mv mkcert-v1.4.4-linux-amd64 /usr/local/bin/mkcert
mkcert -install2. Generate Certificates:
mkcert skypoint.local api.skypoint.local localhost 127.0.0.13. Create Kubernetes Secret:
kubectl create secret tls skypoint-tls \
--cert=skypoint.local+3.pem \
--key=skypoint.local+3-key.pem \
-n skypoint4. Clean Up Certificate Files:
rm skypoint.local+3.pem skypoint.local+3-key.pem5. Restart Ingress Controller:
kubectl rollout restart deployment ingress-nginx-controller -n ingress-nginx
kubectl rollout status deployment ingress-nginx-controller -n ingress-nginx6. Verify:
curl https://skypoint.local
curl https://api.skypoint.local7. Access Application:
- Frontend: https://skypoint.local
- Backend API: https://api.skypoint.local
🔄 Restart your browser after creating certificates to ensure they're recognized.
1. Build and Push New Image:
docker build -t <your-dockerhub-username>/frontend:2.0 .
docker push <your-dockerhub-username>/frontend:2.02. Update Deployment YAML:
Update the image tag in frontend/deployment.yaml and commit to Git:
spec:
containers:
- name: frontend
image: <your-dockerhub-username>/frontend:2.03. ArgoCD Auto-Sync: ArgoCD automatically detects changes and syncs the deployment.
4. Manual Sync (if needed):
argocd app sync skypoint
# Or
kubectl rollout restart deployment frontend -n skypointkubectl get pods -n skypoint
kubectl get svc -n skypoint
kubectl get ingress -n skypoint
kubectl get postgrescluster -n skypointkubectl logs -n skypoint -l app=frontend --tail=50
kubectl logs -n skypoint -l app=backend --tail=50Use these test cards at https://skypoint.local:
- Success: 4242 4242 4242 4242
- Requires authentication: 4000 0025 0000 3155
- Declined: 4000 0000 0000 9995
kubectl describe pod <pod-name> -n skypoint
kubectl logs <pod-name> -n skypointkubectl describe ingress skypoint-ingress -n skypoint
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
ps aux | grep "minikube tunnel"kubectl get postgrescluster skypoint-db -n skypoint
kubectl get secret skypoint-db-pguser-skypoint -n skypoint -o yaml
kubectl exec -it <backend-pod> -n skypoint -- python manage.py dbshellkubectl get secret skypoint-tls -n skypoint
kubectl get secret skypoint-tls -n skypoint -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -datesIf certificates expired, regenerate and recreate the secret.
argocd app get skypoint
argocd app sync skypoint --force
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-server# Delete ArgoCD application
kubectl delete application skypoint -n argocd
# Delete namespace (removes all resources)
kubectl delete namespace skypoint
# Stop minikube tunnel (Ctrl+C in tunnel terminal)
# Optional: Delete minikube cluster
minikube delete- Secrets: Never commit secrets to Git. Create them manually in the cluster.
- Certificates: Certificate files should not be committed to version control.
- Minikube Tunnel: Must remain running for ingress to work.
- Browser Cache: Clear browser cache or restart browser if HTTPS isn't working immediately.
- Database Backups: Configure pgBackRest for production use.