{"id":1267,"date":"2025-11-21T19:23:40","date_gmt":"2025-11-22T03:23:40","guid":{"rendered":"https:\/\/blog.ajgonzo.com\/?p=1267"},"modified":"2025-11-21T19:27:18","modified_gmt":"2025-11-22T03:27:18","slug":"taming-minime-lessons-from-turning-a-workhorse-into-a-real-docker-host","status":"publish","type":"post","link":"https:\/\/blog.ajgonzo.com\/?p=1267","title":{"rendered":"Taming MiniMe: Lessons From Turning a Workhorse Into a Real Docker Host"},"content":{"rendered":"\n<p>I started this whole MiniMe (My over-engineered home ITX mini pc server) Docker project thinking it would be simple. MiniMe is a beast of a machine \u2014 fast NVMe storage, a ton of RAM, and enough CPU headroom to run whatever I throw at it. It was more of a test bed originally over the years, but scaling it up even in a home setting was a story to tell. Naturally, I assumed Docker would glide along without complaint&#8230; what a fool I was!<\/p>\n\n\n\n<p>But like anyone who has dipped their toes into homelab territory knows, raw hardware power doesn\u2019t automatically translate to a smooth experience. The real complexity hides in the layers under the surface \u2014 networking, proxies, filesystems, and the countless assumptions each component makes about how the world should work. <\/p>\n\n\n\n<p>I should pre-empt this that I am a seasoned engineer that has been working on large systems, small systems, enterprise level platforms for over 20 years. I would not consider myself an amateur in the least. However there is a difference between doing this stuff during my day job in small increments or with a team rather than being alone in the ocean of my home infrastructure.<\/p>\n\n\n\n<p>What follows is the journey of getting MiniMe stable as a Docker host, the concepts I ran into, and the lessons I think others might appreciate if they\u2019re walking the same path. This is my catharsis that maybe if inspired I&#8217;ll dig deeper into some of the trials.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>WSL2: Convenient\u2026 Until It Isn\u2019t<\/strong><\/h2>\n\n\n\n<p>Because MiniMe runs Windows as the main OS, I decided to lean into the common approach: Docker Desktop + WSL2. It\u2019s lightweight, it\u2019s fast enough, and it\u2019s ridiculously convenient.<\/p>\n\n\n\n<p>Convenient, however, doesn\u2019t always mean stable.<\/p>\n\n\n\n<p>As my environment grew \u2014 more containers, more networking layers, more storage needs \u2014 WSL2 began showing its quirks. It would:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Detach or hide the Docker socket<\/li>\n\n\n\n<li>Lose track of the Docker engine after an update<\/li>\n\n\n\n<li>Mount filesystems inconsistently<\/li>\n\n\n\n<li>Or just\u2026 drift out of alignment for unclear reasons<\/li>\n<\/ul>\n\n\n\n<p>When WSL thinks Docker is \u201cnot really running,\u201d everything above it collapses. Portainer can\u2019t connect, management containers break, and your entire system feels like it forgot who it is; a general techno identity crisis.<\/p>\n\n\n\n<p><strong>Lesson learned:<\/strong> WSL2 is fantastic for smaller setups or development work. But once you start layering identity, media servers, or advanced network routing, the stability trade-offs become real. Convenience hides complexity \u2014 until that complexity wakes up and asks for attention. All roads lead back to less layers of extrapolation and sometimes it really is best to just run on Linux without virtualization.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>The Docker Socket: The Single Point of Confusion<\/strong><\/h2>\n\n\n\n<p>This was one of my biggest recurring issues.<br>A surprising number of tools rely heavily on <code>\/var\/run\/docker.sock<\/code>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Portainer<\/li>\n\n\n\n<li>Monitoring tools<\/li>\n\n\n\n<li>Auto-update services<\/li>\n\n\n\n<li>Containers that need to inspect other containers<\/li>\n<\/ul>\n\n\n\n<p>When the socket goes missing or becomes unreachable, the entire management plane essentially loses sight of the system \u2014 even while the containers themselves might still be running fine.<\/p>\n\n\n\n<p>It\u2019s the quiet kind of failure that wastes hours.<\/p>\n\n\n\n<p><strong>Lesson learned:<\/strong> If your management stack depends on something fragile (like Docker Desktop\u2019s socket behavior inside WSL2), expect cascading weirdness. Build with that fragility in mind or plan for recovery steps when things fall apart.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Reverse Proxying: The Real Rabbit Hole<\/strong><\/h2>\n\n\n\n<p>Reverse proxying is simple&#8230; until it isn\u2019t.<\/p>\n\n\n\n<p>MiniMe ended up running under a layered system:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Nginx on Windows<\/li>\n\n\n\n<li>SWAG (favored child of docker built nginx) in Docker<\/li>\n\n\n\n<li>Authentik for identity<\/li>\n\n\n\n<li>Firewalls enforcing DNS and SSL<\/li>\n\n\n\n<li>Media apps trying to stream through all this without breaking<\/li>\n<\/ul>\n\n\n\n<p>The main challenge?<br><strong>How do you wrap identity and security around the UI without breaking the APIs or the streaming endpoints?<\/strong><\/p>\n\n\n\n<p>Some devices assume direct access.<br>Some devices break when headers change.<br>Some don\u2019t handle redirects gracefully.<\/p>\n\n\n\n<p>And some \u2014 especially media clients \u2014 simply do not want to authenticate.<\/p>\n\n\n\n<p>Balancing usability, security, and compatibility is a constant dance.<\/p>\n\n\n\n<p><strong>Lesson learned:<\/strong> Never assume all clients behave like browsers. Media apps, smart TVs, and streaming tools behave differently, and treating them the same guarantees pain. Protect the UI. Let the streams breathe. Protection through monitoring, automation and obscurity; break down the walls.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Filesystems: The Hidden Source of Chaos<\/strong><\/h2>\n\n\n\n<p>Mounting network storage is straightforward\u2026 unless the Linux subsystem, the Windows OS, Docker, and the NFS server each interpret the filesystem slightly differently. I have gone through the layers of extra drives, then RAID arrays of whatever drives i had, or drivepool of variable sized and speed drives to ultimately coming back home to RAID5 or robust Synology type systems utilizing their proprietary RAID flavor.<\/p>\n\n\n\n<p>Symptoms included:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Slow scans<\/li>\n\n\n\n<li>Transcoding delays<\/li>\n\n\n\n<li>Boot time race conditions<\/li>\n\n\n\n<li>File locks<\/li>\n\n\n\n<li>Cross OS permission issues<\/li>\n\n\n\n<li>SMB overhead<\/li>\n\n\n\n<li>SMB virtual translation<\/li>\n\n\n\n<li>Timeouts<\/li>\n\n\n\n<li>Containers restarting because of missing files that weren\u2019t actually missing<\/li>\n<\/ul>\n\n\n\n<p>The fix ended up being a mix of proper NFS tuning, understanding how WSL performs its mount lifecycle, and picking the right way to bind volumes inside Docker.<\/p>\n\n\n\n<p><strong>Lesson learned:<\/strong> Storage architecture matters just as much as CPU or RAM. If your filesystem is inconsistent or slow, the apps on top will behave in unpredictable, confusing ways.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Disaster Recovery: A Smarter Approach<\/strong><\/h2>\n\n\n\n<p>I wanted the ability to fail over to my Synology NAS if MiniMe ever had issues. The naive approach was \u201cjust run everything on the NAS too,\u201d but running heavy apps on network storage isn\u2019t great. Or more specifically, run segmented non redundant nodes all over the damn place. Simply said, I would never prescribe in my day job some of the &#8220;solutions&#8221; to my clients\/customers that i whimsically utilized in my homelab. It eventually kicked in why i do the things i do in large environments and it absolutely was worth it to do it even in micro or nano environments.<\/p>\n\n\n\n<p>What worked better was:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Syncing configs and metadata<\/li>\n\n\n\n<li>Keeping container images standardized<\/li>\n\n\n\n<li>Treating the NAS as a warm standby, not a full clone<\/li>\n\n\n\n<li>knowing that burning money on hacky hardware adds up in cost vs just buying a correct network appliance (something something #Netgate6100sFTW)<\/li>\n<\/ul>\n\n\n\n<p>This makes failover faster, cleaner, and without all the overhead of a full second environment running constantly.<\/p>\n\n\n\n<p><strong>Lesson learned:<\/strong> DR for a homelab isn\u2019t about duplicating your entire stack. It\u2019s about safeguarding the \u201cbrains\u201d \u2014 config files, state directories, and metadata \u2014 so you can rebuild quickly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Load Balancing and Failover: The Architectural Turning Point<\/strong><\/h2>\n\n\n\n<p>When I started exploring automatic failover and smart routing, the conversation shifted into questions like:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Should Traefik or HAProxy be the \u201cbrain\u201d of the proxy layer?<\/li>\n\n\n\n<li>Where should SSL terminate?<\/li>\n\n\n\n<li>Should Windows stay in the chain, or should Docker handle everything?<\/li>\n\n\n\n<li>Should APIs fail over automatically?<\/li>\n<\/ul>\n\n\n\n<p>These weren\u2019t bugs to fix \u2014 they were architectural decisions. This is a whole series of posts that i wont even try and talk about in this high level overview.<\/p>\n\n\n\n<p><strong>Lesson learned:<\/strong> Before adding failover, decide which system is responsible for routing intelligence. Multiple proxies all trying to be clever at once leads to some of the most frustrating problems you\u2019ll ever troubleshoot.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Image Pull Errors: The Unseen Architecture Problem<\/strong><\/h2>\n\n\n\n<p>Occasionally Docker would refuse to pull an image with an error like:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>\u201cNo matching manifest for linux\/amd64.\u201d<\/p>\n<\/blockquote>\n\n\n\n<p>It always feels like something must be wrong with your system \u2014 but usually the issue is that the image itself:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Doesn\u2019t support the architecture<\/li>\n\n\n\n<li>Has broken tags<\/li>\n\n\n\n<li>Or is missing multi-arch builds<\/li>\n<\/ul>\n\n\n\n<p>This happened more than once, especially with community images. It fundamentally bugged me as I feel like when i pick a product i get it and i patch on a schedule and the world makes sense. But, when dealing with images, virtualization, containers etc that ease and convenience can become a explosive device at any moment; you MUST plan for it.<\/p>\n\n\n\n<p><strong>Lesson learned:<\/strong> If a container won\u2019t pull, check the image metadata before trying to fix your host. Sometimes the container is the problem, not you.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Where Everything Landed<\/strong><\/h2>\n\n\n\n<p>Today, MiniMe is finally what I wanted it to be:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A stable Docker host<\/li>\n\n\n\n<li>With a clean proxy architecture<\/li>\n\n\n\n<li>Proper identity routing<\/li>\n\n\n\n<li>Tuned network mounts<\/li>\n\n\n\n<li>Failover planning<\/li>\n\n\n\n<li>Security and hardening to the nth degree<\/li>\n\n\n\n<li>Monitoring and Automated Governance across the board<\/li>\n\n\n\n<li>And a manageable, predictable ecosystem<\/li>\n<\/ul>\n\n\n\n<p>It took experimentation, a few headaches, and a lot of untangling assumptions, but it\u2019s now genuinely stable&#8230;. <strong><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-custom-primary-color\">till i break it again.<\/mark><\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Final Thoughts: A Homelab Is a System, Not a Stack<\/strong><\/h2>\n\n\n\n<p>This project taught me something important:<\/p>\n\n\n\n<p>Your homelab isn\u2019t \u201cjust some Docker containers you run.\u201d<br>It\u2019s a system \u2014 a living, interconnected ecosystem of storage, networking, authentication, routing, and applications.<\/p>\n\n\n\n<p>And systems require architecture.<\/p>\n\n\n\n<p>Most of the troubleshooting wasn\u2019t about fixing bugs. It was about discovering the assumptions each layer made \u2014 and then designing the environment so those assumptions didn\u2019t conflict.<\/p>\n\n\n\n<p>If you\u2019re building your own homelab \u2014 especially if you\u2019re mixing Windows, WSL2, Docker, and multiple proxy layers \u2014 expect to spend some time learning those layers deeply. It\u2019s worth it. Eventually, everything clicks, and the result feels incredibly satisfying. Even if you think (like myself) that you have seen it all and done it all; it is sizably different when you are the one tying it all together absolutely and seeing the negative effects of each problem.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I started this whole MiniMe (My over-engineered home ITX mini pc server) Docker project thinking it would be simple. MiniMe is a beast of a machine \u2014 fast NVMe storage, a ton of RAM, and enough CPU headroom to run whatever I throw at it. It was more of a test bed originally over the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1269,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[373,374],"tags":[],"class_list":["post-1267","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-interests","category-tech"],"_links":{"self":[{"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=\/wp\/v2\/posts\/1267","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1267"}],"version-history":[{"count":4,"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=\/wp\/v2\/posts\/1267\/revisions"}],"predecessor-version":[{"id":1273,"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=\/wp\/v2\/posts\/1267\/revisions\/1273"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=\/wp\/v2\/media\/1269"}],"wp:attachment":[{"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1267"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1267"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ajgonzo.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1267"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}