You paste a docker run command from a blog post into your terminal. It works. Your service comes up, you forget about it, and six months later your server reboots after a power outage. The container is gone. You have no idea what ports you mapped, what volumes you mounted, or what environment variables made the thing actually function. You’re now reverse-engineering your own homework.
If this has happened to you, you’re not bad at Docker. You just ran into the natural limit of a workflow that was never designed to survive a reboot in the first place.
The One-Off Command Problem#
A docker run command is not a deployment
. It’s a transaction. The moment it executes, the instructions that created the container exist only in your shell history, if they exist anywhere at all.
Think about what actually happens when you run this command:
docker run -d --name plex \
-p 32400:32400 \
-v /mnt/media:/media \
-e PUID=1000 \
-e PGID=1000 \
plexinc/pms-dockerDocker builds a container from an image and configures it with the flags you provided. But nothing about why you chose port 32400, why /mnt/media is mounted where it is, or why those environment variables matter gets written down anywhere durable. The container itself has a running configuration, but that configuration lives only inside Docker’s internal state, not in a file you can read, edit, or restore from.
When the container dies (crash, reboot, a botched docker system prune), you’re not restoring a known state. You’re trying to remember one. That’s the core failure. Recovery becomes a guessing game instead of a repeatable process.
Why It Feels Like a VM and Why That’s the Trap#
Home lab users often carry over intuition from virtual machines, and it’s an easy mistake to make. A VM feels like a persistent little computer. You SSH in, install a package, tweak a config file, and it just stays that way until you touch it again.
Containers
look similar on the surface. You can docker exec -it plex bash, edit a file inside the container, and the change sticks around as long as the container is running. So it’s tempting to treat the container the same way you’d treat a VM: as a stateful thing that accumulates changes over time.
This is where things go wrong. A container is meant to be disposable. The image defines what the container should look like when it starts. Anything you change by hand inside a running container exists only in that container’s writable layer, and it disappears the instant the container is removed. If you’ve been manually patching config files inside a running container instead of updating the image or its configuration, you’ve built something that only works as long as that exact container never gets destroyed. That’s not resilience. That’s fragility with extra steps.
The virtual machine mental model tells you to nurse the instance along. The container model tells you to make the instance replaceable. These are opposite philosophies, and if you’re mentally running the VM model on top of container tooling, you’re going to be surprised every time a container needs to be recreated.
Turning Commands Into Configuration#
The fix isn’t complicated, and it doesn’t require learning an orchestration platform. It requires writing your configuration down.
A docker-compose.yml file takes everything that was previously trapped in a shell command, or worse, in your memory, and puts it into a single, readable, version-controlled file:
services:
plex:
image: plexinc/pms-docker
container_name: plex
ports:
- "32400:32400"
volumes:
- /mnt/media:/media
- ./plex-config:/config
environment:
- PUID=1000
- PGID=1000
restart: unless-stoppedNow the ports, volumes, and environment variables live in one place, in plain text, checked into a Git repository if you want. If the container disappears, you don’t reconstruct anything from memory. You run docker compose up -d and Docker recreates the exact same container from the exact same definition.
This is the real difference between docker compose and docker run. It’s not that Compose is more powerful, though it is once you have multiple services talking to each other. It’s that Compose forces you to declare your intent before you run anything, instead of improvising it in a terminal and hoping you remember the improvisation later.
The Part People Still Get Wrong#
Writing a docker-compose.yml file solves the documentation problem, but it doesn’t automatically solve the disposability problem. If your container’s data, such as a database, media library metadata, or application state, lives inside the container’s own filesystem, destroying and recreating that container still destroys your data along with it.
This is why separating persistent data into named volumes or host-mounted directories matters as much as writing the Compose file itself. In the example above, /mnt/media and ./plex-config live outside the container. You can delete the plex container entirely, run docker compose up -d again, and the container comes back with all of its data intact, because the data was never actually inside the container to begin with.
This is what actually makes a container disposable. Not the fact that it’s small or starts quickly, but the fact that destroying it costs you nothing. If tearing down a container makes you nervous, that’s a signal that something the container shouldn’t own is still trapped inside it.
Where This Leaves You#
At this point you have a docker-compose.yml file that documents your setup and volumes that keep your data safe from container churn. That’s a real improvement over a forgotten shell command, and for a single service, it’s often enough.
But most home labs don’t run one service. They run a dozen, and those services start depending on each other, need updates on different schedules, and eventually need a plan for what happens when the whole host itself needs to be rebuilt, not just one container on it.
Featured image by Julia Taubitz on Unsplash

