Hosting localhost: Modern Self-hosted Blog

#howto#blog#CI/CD

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

TechDesc
Next.js + MDXStatic site generation
DecapCMS at /admin for visual editing
GitContent store (no DB required)
Github AuthAuth for CMS (via token or OAuth)
JenkinsPipeline for CI/CD on push
NGINXServes static assets
CloudflareTunnel to expose port 80 without public IP

Development

Install dependencies

npm install

Run dev server

npm run dev

Preview Site: http://localhost:3000


Writing Posts

Option 1: Use Decap CMS

  1. Visit http://localhost:3000/admin
  2. Log in with GitHub (OAuth or token)
  3. Create/edit posts
  4. 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.sh to copy files to NGINX root
    • visudo permissions: jenkins ALL=(ALL) NOPASSWD: /usr/local/bin/deploy_blog.sh

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 nginx

3. 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:404

Both intervolz.com and www.intervolz.com need to be declared separately to avoid broken URLs.

One-time DNS Setup

cloudflared tunnel route dns intervolz-tunnel intervolz.com

Run at boot:

sudo cloudflared service install
sudo systemctl enable --now cloudflared

Check Status and restart cloudflared

sudo systemctl status cloudflared
 
sudo systemctl enable --now cloudflared

Build & Export

npm run build

Output is in /out. Jenkins deploys this directory.


References