A few years ago, I decided to turn an old laptop gathering dust into a NAS (Network Attached Storage) system. I thought it would be straightforward—throw some Linux on it, maybe containerize a few services with Docker, and call it done. Turns out, reality had other plans.
That project taught me more about Docker than any tutorial ever could. Not because everything went smoothly, but because it didn’t. When you’re running containerized services on hardware that wasn’t designed for it, in an operating system you’re still learning, things break in interesting ways. And those breaks? They’re the best teachers.
Understanding What Docker Actually Does (And Doesn’t)

Before I started this project, I thought Docker was a magic box. You put code in, it works everywhere, problem solved. That’s not wrong, exactly, but it’s incomplete.
Docker isolates your application and its dependencies into a container. That’s genuinely useful. But isolation isn’t magic—it’s just a clever use of Linux kernel features like namespaces and cgroups. When something goes wrong, you need to understand those underlying mechanisms.
I learned this the painful way when I ran out of storage on my NAS. My Docker containers were working fine individually, but I didn’t understand how Docker layers work, how images stack on top of each other, or why a simple pull of a new image could suddenly consume gigabytes of space. I was treating Docker like a black box instead of understanding what was actually happening inside it.
The lesson here is practical: Docker abstracts away complexity, which is its job, but you still need to know what’s being abstracted. When you run docker ps, you need to understand what those processes are actually doing on your Linux system. When you map volumes between the host and container, you need to grasp how the filesystem actually works. Otherwise, you’re just guessing when problems show up.
Storage and Resource Management Will Bite You
Running Docker containers on a laptop with limited RAM and disk space is humbling. I set up a few services—a media server, a backup system, and a monitoring tool—each in its own container. They all ran. They all worked. Until they didn’t.
The first issue was storage. Docker images can be enormous. A single container image might be 2GB or more. When you pull multiple images, they can share base layers, but that sharing only goes so far. I ended up with 50GB of images on a laptop with 256GB of storage. That sounds like plenty until you remember the NAS also needs to actually store data.
The second issue was memory. I didn’t set resource limits on my containers initially. One malfunctioning service could consume all available RAM, and suddenly the entire system would grind to a halt. Linux would start killing processes trying to free up memory, and I’d have no idea which container was the culprit.
Here’s what I learned to do:
- Always set memory limits on containers using the
--memoryflag. Be specific. Don’t just hope it’ll work out. - Monitor disk usage aggressively. Use
docker system dfto see what’s actually taking up space. - Clean up old images and containers regularly. They stick around by default, consuming resources you probably forgot about.
- Understand the difference between persistent volumes and container storage. A container’s filesystem is temporary and ephemeral—that’s by design.
These aren’t advanced Docker concepts. But living with the consequences of ignoring them taught me their importance in ways that reading documentation never would.
Networking Is More Complex Than It Looks
I assumed that once Docker containers were running, they’d just talk to each other and the outside world smoothly. Wrong again.
By default, Docker creates a bridge network. Containers on that network can reach each other by hostname, which is convenient. But the host can’t reach containers on that bridge network the same way. Port mapping works fine for external connections, but internal communication between services requires understanding how Docker’s DNS resolution works.
I spent an afternoon debugging why my monitoring container couldn’t reach my media server container, both running on the same machine. They weren’t on the same network. I’d spun them up at different times, and they’d landed on different default networks. The fix was trivial—explicitly create a custom network and put both containers on it—but I had to understand the problem first.
Then there was the fun of accessing my NAS from other machines on my home network. Port forwarding at the Docker level is one thing. Firewall rules on the host are another. Understanding which ports were open, where the actual listening was happening, and what Docker was doing versus what Linux was doing—that took real thought.
The takeaway: Docker containers don’t exist in isolation from the host’s network stack. You need to understand how Docker networks map to Linux network interfaces, how ports are exposed, and how traffic flows. Otherwise, you end up with services that work fine when accessed locally but mysteriously fail from other machines.
Logs and Debugging Are Your Friends
When a containerized service breaks, you can’t just SSH into the system and start poking around the way you might with a traditionally installed application. You have to use Docker’s tools to inspect what’s happening.
docker logs is your lifeline. When a container crashes or behaves unexpectedly, check the logs first. But logs only help if the application inside the container is actually writing meaningful output. Some services are silent by default. Some log to files inside the container that you can’t easily access. Some have debug modes you need to enable.
I also learned to use docker exec to drop into a running container and poke around. It’s not SSH, but it lets you inspect the filesystem, check running processes, and verify configurations. For a NAS project, this was invaluable when troubleshooting.
The broader lesson: Docker doesn’t eliminate the need for debugging skills. It just changes where and how you apply them. You’re not looking at system logs or process lists in the traditional way. You’re using Docker-specific tools to see inside containers. The mental model needs to shift.
Persistence Requires Deliberate Planning
This one seems obvious in retrospect, but I learned it by losing data. Containers are ephemeral by design. If a container is deleted, its filesystem goes with it. For a NAS, that’s unacceptable. You need your data to stick around.
Docker volumes are the answer, but you have to set them up correctly. There’s a difference between named volumes (managed by Docker) and bind mounts (directories from the host). Both have their uses, but I mixed them up initially, storing data in containers instead of properly persisting it.
The lesson here is about architecture. Before you spin up a container, think about what data needs to survive a container restart or removal. Set up volumes explicitly. Document them. Don’t just let Docker scatter data around your filesystem.
Building that DIY NAS from an old laptop was frustrating at times, but Docker was integral to making it work. Once I stopped treating Docker as magic and started understanding how it actually operated—how it used Linux features, managed resources, and isolated processes—everything got easier. The project taught me that tools like Docker are incredibly useful, but they work best when you understand the layers beneath them. That’s the real skill: knowing not just how to use the tool, but why it works the way it does.
Source: I built a NAS from an old laptop—here’s what Linux taught me the hard way