This commit is contained in:
@@ -21,7 +21,7 @@ The requirements:
|
||||
- Git hosting for personal projects and showing off.
|
||||
- A blog platform - you're **here**
|
||||
- Infrastructure monitoring
|
||||
- Global accessibility via HTTPS
|
||||
- Accessible anywhere via HTTPS
|
||||
- Automated deployments
|
||||
|
||||
## The Plumbing
|
||||
@@ -32,38 +32,45 @@ I bought this domain for 2 years at the very likeable price of 5 dollars. I ran
|
||||
|
||||
Everything runs in Docker containers, each with its own compose file for isolation and maintainability. I really like the whale logo.
|
||||
|
||||
- **Immich:** Self-hosted photo backup with a mobile app. Deploying it went first and I only had one issue - port mappings changed between versions, requiring adjustments after some googling.
|
||||
- **Immich:** Self-hosted photo backup with a really nice mobile app. Deploying it went first and I only had one issue - port mappings changed between versions, requiring adjustments after some googling.
|
||||
|
||||
- **Gitea:** Git hosting with a web interface, issue tracking, and webhook support. Backed by PostgreSQL for reliability. SSH access configured on a non-standard port to avoid conflicts.
|
||||
- **Gitea:** GitHub but it's sitting above my TV and making fan noises every now and then. Sure, GitHub is free but this one is my own.
|
||||
|
||||
- **Nginx Proxy Manager:** Handles reverse proxying and SSL certificate management. The interface makes it trivial to add new services and request Let's Encrypt certificates. Initially attempted HTTP-01 challenges for SSL validation, but DNS propagation delays caused failures. Switched to DNS-01 challenges using Cloudflare's API, which proved more reliable.
|
||||
- **Nginx Proxy Manager:** Handles reverse proxying and SSL certificate management. Makes it trivial to add new services and request Let's Encrypt certificates. Handles anything accessible on the outside.
|
||||
|
||||
- **Portainer:** Web-based Docker management. Useful for quick container inspection without SSH access. I might keep it, I might not.
|
||||
- **Portainer:** Web-based Docker management. Useful for getting quick container info without SSH access. I might keep it, I might not.
|
||||
|
||||
- **Uptime Kuma:** Kind of unnecessary but I was having fun. Monitors all services and tracks uptime statistics. Provides notifications when services become unavailable.
|
||||
|
||||
- **DDNS Updater:** Synchronizes dynamic IP addresses with Cloudflare DNS records. This proved essential given that I don't have a static IP but ended up causing me a lot of issues when I forgot to make it update the correct place.
|
||||
- **A DIY Simple DDNS Updater:** Synchronizes dynamic IP addresses with Cloudflare DNS records. This proved essential given that I don't have a static IP but ended up causing me a lot of issues when I forgot to make it update the correct place.
|
||||
|
||||
- **Tailscale:** Mesh VPN providing secure access to internal services without port forwarding. Also serves as a workaround for NAT loopback limitations.
|
||||
|
||||
- **This Blog:** Built with SvelteKit, served via nginx, deployed automatically via webhooks. The entire deployment pipeline runs on the same server.
|
||||
|
||||
## The Networking Problems
|
||||
## The Kicker
|
||||
|
||||
### Two NATs For the Price of One
|
||||
The initial topology placed the server behind both the ISP gateway and a personal router, creating double NAT. Port forwarding rules existed on both devices. Services worked from the local network but were unreachable from external connections.
|
||||
I have an old 1TB external HDD in good health (checked) that I decided to use for incremental backups of my photos. Automated daily backups run at 0200 hours via systemd timer, mounting the drive for the duration of the backup and keeping 7 days of history. The idea being that I get to have a "2" in the 3-2-1 backup rule and the drive spins down when not in use.
|
||||
|
||||
The solution involved eliminating the router layer entirely and connecting the server directly to the gateway.
|
||||
## The (Mostly Networking) Problems
|
||||
|
||||
### The Changing IP Mystery
|
||||
Services remained accessible from the local network but failed from external networks. Cloudflare returned errors 520 and 522. Investigation revealed a discrepancy between the actual public IP and what DNS records pointed to.
|
||||
The root cause: the DDNS updater was configured for Namecheap (where I bought the domain), but DNS had been migrated to Cloudflare for the free API access. When the public IP changed, the actual DNS records never updated. Reconfiguring the DDNS updater with Cloudflare credentials fixed it.
|
||||
### SSLn't
|
||||
The initial deployment of Immich was smooth sailing all the way until I decided to try accessing it through my phone on 5G. Then started the troubles with NPM (nginx proxy manager, not the node package manager). Either due to improper routing and networking configuration or who knows what, I couldn't get a certificate with the HTTP challenge method. This lead to moving nameservers to Cloudflare and using the API to request certificates with the DNS method.
|
||||
|
||||
### Two NATs for the price of one
|
||||
The initial "topology" placed the server behind both my ISP gateway and my router, creating double NAT. Port forwarding rules existed on both devices. Services worked from the local network but were unreachable from external.
|
||||
|
||||
The solution involved me facepalming when I realized I could eliminate one layer entirely and connecting the server directly to the gateway. This was my first "big" breakthrough.
|
||||
|
||||
### The only constant (IP) is change
|
||||
While troubleshooting why I can't reach anything externally, Claude went throught some logs and saw I had 3 IP changes in the span of a day.
|
||||
The root cause: the DDNS updater was configured for Namecheap (where I bought the domain), but DNS had been migrated to Cloudflare for the free API access. When the public IP changed, the actual DNS records never updated.
|
||||
|
||||
### NAT Loop-de-loop(back)
|
||||
The gateway doesn't support NAT hairpinning, preventing access to services via their public domains from the internal network. For the time being I've resorted to editing the hosts files on local machines.
|
||||
The gateway I have doesn't support NAT hairpinning (or at least I didn't find a weirdly named option for it). This prevemtns accessing my services with their public domains from the internal network. For the time being I've resorted to editing the hosts files on local machines. Tailscale is also an option as mentioned earlier.
|
||||
|
||||
### Not killing my old external HDDs - only one I haven't solved
|
||||
Automated daily backups run via systemd timer, mounting an external USB drive, backing up critical data, maintaining seven days of history, and unmounting the drive to allow spindown. The spindown doesn't work quite right so at the time of writing this I'm still working on it.
|
||||
### Not killing my old external HDD - only one I haven't solved
|
||||
It spins down when not in use. Or at least that was the idea. While troubleshooting DNS at around 3AM I wend to unplug a LAN cable and heard the little HDD idling. This means the spindown doesn't work quite right so the HDD stays idling at all times which would kill it rather quickly. At the time of writing this I'm still working on it.
|
||||
|
||||
|
||||
## AI-Assisted Development
|
||||
@@ -76,13 +83,13 @@ Claude did all the heavy lifting on favorites such as interpreting errors, handl
|
||||
|
||||
**Network topology matters.** NATs (yes, multiple), forwarding ports, proxying DNS, etc. Crouching over LAN cables in your underwear on a freezing night. I have a lot to learn.
|
||||
|
||||
**Dynamic DNS isn't optional.** I'm sure ISPs change IP addresses just to mess with you. Automated DDNS definitely saved me a couple times.
|
||||
**Dynamic DNS is a fact of life** I'm sure ISPs change IP addresses just to mess with you. Automated DDNS definitely saved me a couple times.
|
||||
|
||||
**Comprehensive logging pays dividends.** Both systemd journal and file-based logs provide different perspectives on service behavior. Even better when you have AI read them for you.
|
||||
**It's loog! It's loog! It's better than bad - it's good!** It's good to have everything dilligently logging its every step. Even better when you have AI read the logs for you.
|
||||
|
||||
**Container orchestration simplifies management.** Docker Compose provides clear service definitions, easy updates, and straightforward rollbacks. I know absolutely none of that but I'll learn in time.
|
||||
**Container orchestration simplifies management.** Claude Code says: *"Docker Compose provides clear service definitions, easy updates, and straightforward rollbacks."* I know absolutely nothing about that but I'll learn in time.
|
||||
|
||||
**VPN access solves edge cases.** Tailscale addresses NAT loopback issues, provides secure remote access, and simplifies network architecture. Wireguard on its own eliminates the need of a 3rd party service so it's on the table.
|
||||
**VPN access solves edge cases.** Tailscale addresses the NAT loopback issue and the "I'm not home right now" issue. It also simplifies network architecture. Wireguard on its own eliminates the need of a 3rd party service so it's on the table.
|
||||
|
||||
## Let's Crunch The Numbers
|
||||
I didn't do this to save money. I did it as a testament to my love for technology and upcycling redundant machines. But it **actually saves money**:
|
||||
@@ -99,7 +106,7 @@ I didn't do this to save money. I did it as a testament to my love for technolog
|
||||
- Static site hosting: €5-20/month
|
||||
- Total: ~€228-408/year
|
||||
|
||||
The financial case is especially strong when you include the lessened time investment with AI assistance. The educational benefits will later appear on my bank balance.
|
||||
The financial case is especially strong when you include the lessened time investment with AI assistance. Learning new skills to me is worth a lot of money especially when I start getting paid for it.
|
||||
|
||||
## So What Now?
|
||||
|
||||
|
||||
Reference in New Issue
Block a user