Multi-Arch CI/CD: Building a Hugo Nginx Container
This write-up covers the transition from local Hugo builds to an automated, multi-architecture (AMD64/ARM64) Docker pipeline using GitHub Actions and Docker Hub.
The Problem
I wanted to simplify my website development. Rather than focusing on infrastructure, I wanted to focus on content. I chose Hugo because it allows me to create simple Markdown files while generating an elegant, high-performance site.
However, traditional deployment often involves manual rsyncing or running Hugo directly on a production server. This creates “it works on my machine” issues and makes architecture-specific deployments (like moving to an ARM64 server) a logistical headache.
The Solution: Immutable Containers
The solution is to package the entire site into a standard Nginx Docker image. We chose Nginx over the built-in hugo server because Nginx is a battle-hardened production web server, whereas hugo server is intended for local development. By using a containerized approach, we ensure the environment is identical from development to production.
Create the Site
After installing Hugo, the setup is straightforward:
| |
1. The Nginx Configuration
To handle Hugo’s “Pretty URLs” (e.g., /posts/my-post/ instead of /posts/my-post.html), we need a custom Nginx configuration. Nginx needs to be told to look for an index.html within a directory if the direct file path isn’t found.
File: default.conf
| |
2. The Dockerfile
Our Dockerfile follows a “slim” pattern. We build the static files outside of Docker in our CI/CD runner and then COPY them in. This keeps our final production image extremely small and secure.
File: Dockerfile
| |
3. The GitHub Actions Pipeline
This is the core of the automation. We use QEMU to emulate different CPU architectures, allowing us to build an ARM64 image (for our server) even though GitHub’s runners use AMD64.
File: .github/workflows/deploy.yml
| |
4. Automated Deployment with Watchtower
Instead of GitHub “pushing” to our server, we use a “pull” model via Watchtower. Watchtower runs on our production server and polls Docker Hub for updates. This is more secure because it doesn’t require us to open SSH ports to the public internet for our CI/CD to function.
File: docker-compose.yml
| |
Summary
With this architecture, the workflow is now fully automated and architecture-agnostic. We focus on content, and the infrastructure handles the rest.