Docker for Beginners: What I Wish I Knew First
My first container ate three hours of work, and it was entirely my fault
I still remember the exact moment. I'd spun up a Postgres container to test a small app, played around with it for an afternoon, then ran docker rm to clean up before moving on. Every table I'd created was just gone. No warning, no recovery. That was my introduction to how Docker actually thinks about storage, and it's the same lesson almost every beginner learns the hard way.
I've been doing DevOps and self-hosting work for a while now, and I still see new users make the exact same handful of mistakes I made. This post is the explanation I wish someone had given me before I touched my first container, instead of the vague "just run docker run" tutorials that skip the parts that actually bite you.
A container is not a lightweight virtual machine
This is the mental model that trips people up first. A VM has its own operating system, its own disk, and it keeps state between reboots the way your laptop does. A container is closer to a process with its own filesystem view. When that process (the container) is removed, everything written inside it during its life is gone unless you explicitly told Docker to keep it somewhere else.
That single distinction explains almost every "beginner surprise" people run into: data loss, config that resets, and images that seem to forget settings after a restart. Once that clicks, the rest of Docker gets a lot less mysterious.
Pitfall #1: Volumes, or the lack of them
If you don't tell Docker where to persist data, it lives inside the container's writable layer. Delete or recreate the container, and it's gone. This is what wiped my Postgres data.
The fix is a named volume or a bind mount. For most self-hosted apps, I reach for named volumes because Docker manages the location for you and it plays nicer with backups.
docker run -d --name mydb -v pgdata:/var/lib/postgresql/data postgres:16
Here, pgdata is a named volume that lives outside the container's lifecycle. You can delete and recreate mydb as many times as you want, and the actual database files stay put on the host.
If you want to see exactly where that data physically lives, or copy it out for a backup, run:
docker volume inspect pgdata
A related trap: bind mounts on Windows and Mac sometimes behave unexpectedly with file permissions, especially for anything running as a non-root user inside the container. If a self-hosted app complains about permission denied on startup, check the ownership of the mounted folder before you assume the app itself is broken.
Pitfall #2: Port mapping confusion
The -p flag trips up almost everyone at first because the order is easy to get backwards.
docker run -d -p 8080:80 nginx
Read this as host:container. The left number, 8080, is what you type into your browser on the machine running Docker. The right number, 80, is what the application inside the container is actually listening on. Mix them up, or forget the flag entirely, and you'll be staring at a browser that refuses to connect while the container logs show everything is fine.
Two things that get people specifically:
- If two containers try to bind the same host port, the second one will fail to start. Check what's already using a port with
docker psbefore assuming your config is broken. - Exposing a port doesn't automatically make it reachable from outside your network. That's a firewall and router question, separate from Docker entirely.
Pitfall #3: Ignoring restart policies
By default, a container does not restart if it crashes or if the host machine reboots. I learned this after setting up a small self-hosted service, walking away for a weekend, and coming back to find it had been down since a power blip on day one.
For anything you actually want running continuously, set a restart policy explicitly:
docker run -d --restart unless-stopped nginx
unless-stopped is the one I default to for almost everything self-hosted. It restarts automatically on crash or reboot, but respects it if you manually stop the container yourself. always will even start it back up if you stopped it manually, which is rarely what you want on a personal server.
Skip raw docker run once you have more than one container
Typing out long docker run commands with a dozen flags gets old fast, and it's easy to forget exactly what flags you used last time. The moment I'm running more than one container that talks to another, I switch to Docker Compose.
services:
app:
image: myapp:latest
ports:
- "8080:80"
volumes:
- appdata:/data
restart: unless-stopped
volumes:
appdata:
Everything above lives in a single docker-compose.yml file, in version control if you want it there, and it starts with one command:
docker compose up -d
This is genuinely the biggest quality-of-life upgrade for anyone self-hosting more than a single app. Most of the self-hosted projects worth running these days ship a compose file already, so you rarely need to build one from scratch.
When something breaks, logs first
New users tend to jump straight to Googling error messages before checking what the container itself is saying. Nine times out of ten, the answer is sitting right there.
docker logs mydb
docker logs -f mydb
The -f flag follows the log in real time, which is useful when you're actively trying to trigger the error again. If the container exits immediately after starting, docker logs is almost always where the real reason shows up, not in some generic error page.
Clean up, or your disk fills up quietly
Images, stopped containers, and unused volumes pile up over time and nobody notices until the disk is nearly full. A periodic cleanup saves you the surprise:
docker system prune -a
Be careful with the -a flag since it removes any image not currently used by a running container. That's fine for a home server but worth thinking twice about on something shared.
The short version
Containers are disposable, so anything you care about needs a volume. Ports map host-to-container, not the other way around. Set a restart policy or your service dies quietly the first time something hiccups. Move to Compose as soon as you're juggling more than one container. And read the logs before you read the internet.
If you'd rather skip the trial and error and just have your self-hosted stack set up correctly the first time, I take on exactly this kind of Docker and DevOps work on Fiverr under hiteshsaini459. Otherwise, keep breaking things on a test box — that's genuinely how this sticks.