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

AspectDetails
FrameworkHugo with PaperMod theme
CI/CDGitLab CI/CD (4-stage pipeline)
StorageSeaweedFS S3 with cross-site replication
SecretsOpenBao with JWT authentication
ClustersNetherlands (primary) + Greece (DR)
DeploymentFully 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 cluster
  • refresh-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

LayerTechnology
Static SiteHugo 0.147.0, PaperMod theme
CI/CDGitLab CI/CD, Docker runners
SecretsOpenBao, JWT/OIDC authentication
StorageSeaweedFS S3, cross-site replication
ContainerNginx, MinIO client
OrchestrationKubernetes (Cilium CNI)
GitOpsArgoCD managing deployments
Certificatescert-manager, Let’s Encrypt

Workflow

To update the site:

  1. Edit content locally or in GitLab web UI
  2. Commit and push to main branch
  3. Pipeline automatically:
    • Builds Hugo site
    • Uploads to SeaweedFS
    • Restarts pods in both clusters
  4. 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