Home Self-Hosting a Static Site on Kubernetes with ArgoCD and GitHub Actions
Post
Cancel

Self-Hosting a Static Site on Kubernetes with ArgoCD and GitHub Actions

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.

  1. Static Site: Built with Jekyll (Chirpy theme).
  2. Web Server: Nginx serves the static content.
  3. Content Delivery: A Kubernetes Deployment with an initContainer handles the synchronization of the site’s built assets.
  4. Networking: Kubernetes Gateway API (HTTPRoute) manages external access via hello.<your-domain>.com.
  5. 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 SealedSecret for SSH credentials.
  • It copies the pre-built _site/ content into an emptyDir volume 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.

This post is licensed under CC BY 4.0 by the author.