I created this for myself and my good friend Matt. At time of writing I also host his blog on my server. PLUG!
This article explains the steps to create and host a modern blog setup from your location.
Includes: Git-backed content, visual editing, automated Jenkins deployment, and NGINX hosting via a Cloudflare Tunnel.
If you don't know what those things are...leave now. Ha! Just kidding. I attempt to explain them below and will respond to emails when I see them.
RE: Title - this is served from an Intel NUC - a hamburger-sized linux computer
Stack Overview
| Tech | Desc |
|---|---|
| Next.js + MDX | Static site generation |
| Decap | CMS at /admin for visual editing |
| Git | Content store (no DB required) |
| Github Auth | Auth for CMS (via token or OAuth) |
| Jenkins | Pipeline for CI/CD on push |
| NGINX | Serves static assets |
| Cloudflare | Tunnel to expose port 80 without public IP |
Development
Install dependencies
npm installRun dev server
npm run devPreview Site: http://localhost:3000
Writing Posts
Option 1: Use Decap CMS
- Visit http://localhost:3000/admin
- Log in with GitHub (OAuth or token)
- Create/edit posts
- Publish → Git commit → CI triggers deploy
Option 2: Manually edit or create .mdx files
Posts live in /content/posts.
Each file has frontmatter (title, date, tags, etc.) and MDX body content.
Deployment Architecture
1. Jenkins Pipeline
Defined in Jenkinsfile, runs on push:
- Installs deps
- Builds site to
out/ - Calls
deploy_blog.shto copy files to NGINX root- visudo permissions:
jenkins ALL=(ALL) NOPASSWD: /usr/local/bin/deploy_blog.sh
- visudo permissions:
2. NGINX Setup (Ubuntu)
Site config in /etc/nginx/sites-available/intervolz.com:
server {
listen 80;
server_name intervolz.com www.intervolz.com;
root /var/www/intervolz;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# this prevents access to dotfiles that sometimes make it into webroots: .git/ .htaccess, .DS_Store, .npmrc, etc.
location ~ /\. {
deny all;
return 404;
}
}Enable with:
sudo ln -s /etc/nginx/sites-available/intervolz.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx3. Cloudflare Tunnel
Exposes local port 80 securely without public IP.
Config (~/.cloudflared/config.yml):
tunnel: YOUR_TUNNEL_ID
credentials-file: /home/YOU/.cloudflared/YOUR_TUNNEL_ID.json
ingress:
- hostname: intervolz.com
service: http://localhost:80
- hostname: www.intervolz.com
service: http://localhost:80
- service: http_status:404Both
intervolz.comandwww.intervolz.comneed to be declared separately to avoid broken URLs.
One-time DNS Setup
cloudflared tunnel route dns intervolz-tunnel intervolz.comRun at boot:
sudo cloudflared service install
sudo systemctl enable --now cloudflaredCheck Status and restart cloudflared
sudo systemctl status cloudflared
sudo systemctl enable --now cloudflaredBuild & Export
npm run buildOutput is in /out. Jenkins deploys this directory.