This portfolio site demonstrates enterprise-grade deployment practices applied to a personal website. Built with Hugo and automatically deployed to two Kubernetes clusters across different countries with content replication.
Overview
| Aspect | Details |
|---|---|
| Framework | Hugo with PaperMod theme |
| CI/CD | GitLab CI/CD (4-stage pipeline) |
| Storage | SeaweedFS S3 with cross-site replication |
| Secrets | OpenBao with JWT authentication |
| Clusters | Netherlands (primary) + Greece (DR) |
| Deployment | Fully automatic on git push |
Architecture
The site follows a GitOps workflow where any push to the main branch triggers a complete build and deployment cycle across both geographic locations.
┌─────────────────────────────────────────────────────────────────┐
│ Git Push (main) │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ GitLab CI/CD Pipeline │
│ ┌─────────┐ ┌─────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Build │──│ Upload │──│ Refresh-NL │──│ Refresh-GR │ │
│ │ (Hugo) │ │ (S3) │ │ (kubectl) │ │ (kubectl) │ │
│ └─────────┘ └─────────┘ └─────────────┘ └─────────────┘ │
└───────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SeaweedFS S3 Storage │
│ ┌─────────────────────────────────────┐ │
│ │ s3://portfolio/ bucket │ │
│ │ (NL Primary) │ │
│ └──────────────┬──────────────────────┘ │
│ │ Auto-Replication │
│ ┌──────────────▼──────────────────────┐ │
│ │ s3://portfolio/ bucket │ │
│ │ (GR Replica) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
┌──────────────────┴──────────────────┐
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ Netherlands (NL) │ │ Greece (GR) │
│ Kubernetes │ │ Kubernetes │
│ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ Nginx Pods │ │ │ │ Nginx Pods │ │
│ │ (2 replicas) │ │ │ │ (2 replicas) │ │
│ └───────────────┘ │ │ └───────────────┘ │
│ 192.168.85.64 │ │ 192.168.58.65 │
└─────────────────────┘ └─────────────────────┘
Pipeline Stages
1. Build Stage
Hugo compiles the site with minification and garbage collection using a custom Docker image (hugo-runner:latest) that includes:
- Hugo v0.147.0 extended
- MinIO client for S3 operations
- OpenBao CA certificates
2. Upload Stage
- Authenticates to OpenBao via JWT (GitLab OIDC)
- Retrieves S3 credentials dynamically
- Syncs built files to SeaweedFS bucket
- SeaweedFS automatically replicates to Greece
3. Refresh Stages (Parallel)
Two parallel jobs restart the Nginx deployments:
refresh-nl: Restarts pods in Netherlands clusterrefresh-gr: Restarts pods in Greece cluster
Each uses dedicated RBAC service accounts with minimal permissions.
Key Components
Hugo Configuration
- Theme: PaperMod with profile mode
- Features: Dark/light toggle, search, RSS/JSON feeds
- Content: Projects, blog posts, about page
GitLab CI/CD
stages:
- build
- upload
- refresh
# Automatic on push to main
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
OpenBao Integration
JWT authentication eliminates static credentials:
- GitLab provides OIDC token
- OpenBao validates and returns S3 credentials
- Credentials scoped to
websites/*path only
RBAC Configuration
Minimal permissions for CI/CD service account:
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "patch", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
Kubernetes Deployment
- Pods: Nginx serving static files from SeaweedFS
- InitContainer: Pulls content from S3 on startup
- Replicas: 2 per cluster for high availability
- Ingress: TLS termination with cert-manager
Technology Stack
| Layer | Technology |
|---|---|
| Static Site | Hugo 0.147.0, PaperMod theme |
| CI/CD | GitLab CI/CD, Docker runners |
| Secrets | OpenBao, JWT/OIDC authentication |
| Storage | SeaweedFS S3, cross-site replication |
| Container | Nginx, MinIO client |
| Orchestration | Kubernetes (Cilium CNI) |
| GitOps | ArgoCD managing deployments |
| Certificates | cert-manager, Let’s Encrypt |
Workflow
To update the site:
- Edit content locally or in GitLab web UI
- Commit and push to main branch
- Pipeline automatically:
- Builds Hugo site
- Uploads to SeaweedFS
- Restarts pods in both clusters
- Site live in ~2 minutes
No manual intervention required.
Benefits
- Geographic Redundancy: Content served from two locations
- Automatic Failover: Either cluster can serve traffic
- Zero-Touch Deployment: Push to deploy
- Secret Security: No static credentials in CI/CD
- GitOps Managed: Infrastructure as code via ArgoCD
- Cost Effective: Runs on existing homelab infrastructure
Results
A production-grade deployment pipeline for a personal portfolio that demonstrates:
- Multi-cluster Kubernetes deployments
- GitOps workflows with ArgoCD
- Secure secret management with OpenBao
- Cross-site storage replication
- Automated CI/CD pipelines
The same patterns scale to enterprise applications.
Skills Demonstrated
- Hugo static site generation
- GitLab CI/CD pipeline design
- Kubernetes RBAC configuration
- OpenBao/Vault JWT authentication
- SeaweedFS distributed storage
- Multi-cluster deployments
- GitOps with ArgoCD
- Docker image building
Meta: This project documents its own infrastructure
