How I Survived Hosting a Publii Static Site on DietPi with Docker, Nginx Proxy Manager & Pure Stubbornness

 This was supposed to be easy. A quick project. Something light. Instead, it was a 3-hour battle of broken paths, DNS trickery, and debugging in the dark.
But I won. And the result? A self-hosted website fully running on a Raspberry Pi, encrypted, reverse-proxied, and blazing fast.
> Here's how it went down, for better or worse.


⚙️ Setup

* **Site Generator**: Publii (on Windows)
* **Host OS**: DietPi on Raspberry Pi 4 (4GB RAM)
* **Docker Container**: `nginx:alpine`
* **Reverse Proxy**: Nginx Proxy Manager (NPM)
* **Domain**: `techmuminkis.muminkis.com` via Cloudflare
* **SSL**: Let's Encrypt
* **Storage Path**: Docker volume → `/usr/share/nginx/html`

---

🧱 Phase 1: The Docker Build That Should've Worked

* Created a Docker volume:

```bash
docker volume create publii-data
```

* Mounted it to an `nginx:alpine` container at `/usr/share/nginx/html`

* Exposed port `40123:80` to not interfere with anything else

* Created a custom Docker network to match NPM (`npm-net`)

Everything looked fine. Logs clean. Nginx alive.
But the site showed a white page — or sometimes nothing at all.

---

🕳️ Phase 2: Realizing the Output Was… Gone

Turns out:

* The `output/` folder in Publii wasn’t being created
* Instead, all site files were in the **`preview/`** folder
* Even worse: I had **deleted `_data` contents** inside the volume while troubleshooting, so nginx was serving air

---

🧪 Phase 3: Total Container Surgery

At this point I:

* Recreated the container
* Changed networks multiple times
* Compared IP ranges and MACs like a forensic analyst
* Manually copied files from `preview/` into the volume
* Checked DNS with:

```bash
curl -v, dig, nslookup, ping, etc
```

---

🌐 Phase 4: The Cloudflare Facepalm

Even when all containers were up and serving the correct content — the site wouldn’t load over HTTPS.
Why?

Because I **forgot to add the CNAME record in Cloudflare**.

After fixing the DNS, Let’s Encrypt in NPM finally validated, and the padlock appeared. But…

---

🧞‍♂️ Phase 5: The “Sync Your Website” Mystery Button Appears

All of this would’ve been easier if Publii behaved. But no:

* There was no **“Sync your website”** button
* No **output folder**
* No “View Website”

It *finally appeared* only after I switched from **Manual Deployment** to **SFTP**.

I entered:

* **Website URL**: `https://techmuminkis.muminkis.com`
* **IP address**: Local IP of DietPi (e.g., `192.168.1.x`)
* **Port**: `22`
* **Login/pass**: DietPi user with SFTP access

Suddenly:

* The **Sync Website** button appeared (bottom-left)
* The **`output/`** folder was properly generated
* The **“View Website”** option showed the actual live site via NPM + SSL + Cloudflare

---

🔐 Final Setup: Self-Hosted and Safe (Right?)

* The final site is served from my DietPi box
* Reverse proxied through Nginx Proxy Manager
* Protected by Cloudflare (including DNS and SSL certs)
* Only port 443 is exposed externally (everything else is internal)
* Cloudflare hides the server IP (no public exposure of Raspberry Pi)
* Let’s Encrypt SSL terminates at NPM — no unencrypted hop

So yes — **it's fast, it's clean, it's secure (within reason), and fully under my control**.
Would I recommend it? If you like pain. Otherwise, just follow this guide.

---

### 🏁 TL;DR Recap

✅ Docker volume
✅ Custom Docker network
✅ Container on nginx\:alpine
✅ Files mounted to `/usr/share/nginx/html`
✅ Exposed port `40123`
✅ Nginx Proxy Manager rule:

* Hostname: `techmuminkis.muminkis.com`
* Forward IP: container IP
* Forward Port: `80`
* SSL: Let’s Encrypt
✅ CNAME DNS in Cloudflare
✅ SFTP Sync in Publii