Introduction
Self-hosting a static site often involves simple S3 buckets or traditional web servers. However, leveraging Kubernetes and GitOps principles provides a robust, scalable, and fully automated environment. This post describes the architecture behind <your-domain>, a Jekyll-based static site served by Nginx on a Kubernetes cluster, managed via ArgoCD and automated through GitHub Actions.
Architecture Overview
The architecture is designed to minimize the need for custom Docker image builds for every content change. Instead, it uses a generic Nginx image and pulls the static content dynamically.
- Static Site: Built with Jekyll (Chirpy theme).
- Web Server: Nginx serves the static content.
- Content Delivery: A Kubernetes
Deploymentwith aninitContainerhandles the synchronization of the site’s built assets. - Networking: Kubernetes Gateway API (
HTTPRoute) manages external access viahello.<your-domain>.com. - GitOps: ArgoCD ensures the cluster state matches the manifests defined in the repository.
The CI/CD Workflow
The automation pipeline, defined in .github/workflows/deploy.yml, handles the build and deployment trigger in a two-stage process.
Stage 1: Build and Asset Sync
When code is pushed to main, GitHub Actions:
- Sets up Ruby and builds the Jekyll site using
bundle exec jekyll build. - Commits the resulting
_site/directory back to the repository. - Uses
[skip ci]in the commit message to prevent an infinite loop of builds.
Stage 2: Manifest Update and Forced Redeploy
Once the assets are updated, the pipeline:
- Pulls the latest commit (including the new
_site/content). - Updates a “restartedAt” timestamp annotation in
gitops/basic/webui-deployment.yaml. - Commits this change, which triggers ArgoCD to detect a “drift” or an update.
Kubernetes Implementation
The deployment strategy is unique because it decouples the web server from the content versioning.
InitContainer Content Sync
The <your-domain>-webui-deployment uses an initContainer to prepare the environment:
- It clones the Git repository using a
SealedSecretfor SSH credentials. - It copies the pre-built
_site/content into anemptyDirvolume shared with the main Nginx container.
Main Container
The main Nginx container mounts the shared volume at /usr/share/nginx/html. Because the GitHub Action updates the restartedAt annotation, Kubernetes performs a rolling restart of the pods, forcing the initContainer to pull the freshest assets from the repository.
1
2
3
4
5
6
7
8
9
initContainers:
- name: init-copy-files
image: alpine/git:latest
command: ["/bin/sh", "-c"]
args:
- |
# ... setup SSH ...
git clone -o origin git@github.com:<your-username>/<your-domain>.git /tmp/<your-domain> &&
cp -r /tmp/<your-domain>/_site/* /html-mount
GitOps with ArgoCD
ArgoCD monitors the gitops/basic directory. When the GitHub Action updates the deployment manifest with a new timestamp, ArgoCD synchronizes the change to the cluster. This “Push-to-Git, Pull-to-Cluster” flow ensures that the live site is always a reflection of the main branch.
Conclusion
This architecture provides a high-performance static hosting environment with the power of Kubernetes networking and the reliability of GitOps. By using an initContainer to sync assets, we avoid the overhead of building and pushing heavy Docker images for every minor text change, resulting in a fast and efficient deployment loop.