docker run is the first command most people learn and the one they understand the least. It does a lot in a single line — pulls an image, creates a container, wires up ports and storage, sets environment variables, and starts the process — and the flags that control all that are easy to fumble.
This guide takes docker run apart with examples you can paste and run. We’ll go flag by flag through the ones you’ll actually use, then put them together into real commands for Nginx, Postgres, and quick one-off tasks.
One thing to get straight up front: docker run always creates a new container. It’s not how you restart something you stopped — that’s docker start. Run it twice and you have two containers.
The anatomy of a docker run command
Every docker run follows the same shape:
docker run [OPTIONS] IMAGE [COMMAND] [ARGS]
Options come first, then the image, then an optional command to run inside the container instead of the image’s default. A complete real example:
docker run -d --name web -p 8080:80 --restart unless-stopped nginx:1.27
That single line says: run the nginx:1.27 image detached, name the container web, publish container port 80 as host port 8080, and restart it automatically unless I stop it. Let’s unpack each flag.
The flags you’ll use constantly
The docker run flags worth memorizing
| -d, --detach | Run in the background and return your prompt. Use for services. |
|---|---|
| -p, --publish | Map a port as HOST:CONTAINER, e.g. -p 8080:80. |
| -v, --volume | Mount storage: a named volume or a host path into the container. |
| -e, --env | Set an environment variable, e.g. -e POSTGRES_PASSWORD=secret. |
| --name | Give the container a readable name instead of a random one. |
| --rm | Delete the container automatically when it exits. Good for one-offs. |
| -it | Interactive terminal: -i keeps stdin open, -t gives a TTY. |
| --restart | Set a restart policy, e.g. unless-stopped. |
-d: run in the background
Without -d, a container runs in the foreground and takes over your terminal until the process ends or you press Ctrl+C. That’s useful when you want to watch output, but for a service you want it out of the way:
# Foreground: terminal is occupied, logs stream live
docker run nginx:1.27
# Detached: runs in the background, prompt returns
docker run -d nginx:1.27
-p: publish a port
A container’s ports are isolated from the host until you publish them. The format is HOST:CONTAINER:
docker run -d -p 8080:80 nginx:1.27
# Browse to http://localhost:8080 — host 8080 maps to Nginx's port 80
-v: mount storage
-v attaches storage to a container so data lives outside it. There are two forms:
# Named volume (Docker manages where it lives on disk)
docker run -d -v db-data:/var/lib/postgresql/data postgres:16
# Bind mount (a specific host folder, absolute path)
docker run -d -v /home/me/site:/usr/share/nginx/html:ro nginx:1.27
A name with no slash (db-data) is a named volume. A path (/home/me/site) is a bind mount of that exact host folder. The :ro suffix makes the mount read-only. The differences matter more than they look — the bind mount vs named volume guide covers when to use each.
-e: set environment variables
Most official images are configured through environment variables. Repeat -e for each one:
docker run -d \
-e POSTGRES_USER=appuser \
-e POSTGRES_PASSWORD=change-me \
-e POSTGRES_DB=appdb \
postgres:16
For many variables, keep them in a file and load it with --env-file env.list instead of a long chain of -e flags.
Real examples you can paste
Nginx serving a local folder
Run a web server that serves files from a folder on your machine, named so it’s easy to manage, and set to survive reboots:
docker run -d \
--name web \
-p 8080:80 \
-v "$(pwd)/site:/usr/share/nginx/html:ro" \
--restart unless-stopped \
nginx:1.27
Put an index.html in a site folder, run this from that folder’s parent, and open http://localhost:8080. The :ro keeps the container from writing to your files.
Postgres with a persistent volume
A database is the case where storage matters most. Use a named volume so the data survives the container:
docker run -d \
--name pg \
-e POSTGRES_USER=appuser \
-e POSTGRES_PASSWORD=change-me \
-e POSTGRES_DB=appdb \
-v pg-data:/var/lib/postgresql/data \
-p 5432:5432 \
--restart unless-stopped \
postgres:16
The POSTGRES_* variables come straight from the official Postgres image documentation — they’re read on first start to create the user and database. Remove and recreate the container all you like; as long as the pg-data volume exists, your data is there.
A one-off command with —rm
When you just want to run something once and not leave a stopped container behind, combine --rm with an interactive terminal:
# Drop into a shell in a temporary Ubuntu container, gone when you exit
docker run --rm -it ubuntu:24.04 bash
# Run a quick throwaway command and clean up
docker run --rm alpine echo "hello from a container"
--rm deletes the container the moment it exits, so these don’t accumulate. The -it pair gives you an interactive terminal for the shell example.
run vs start: a common mix-up
Because docker run creates a new container every time, using it to “restart” something leaves you with duplicates and orphaned state. The right commands once a container exists:
docker stop web # stop a running container, keep it around
docker start web # start it again with all its original settings
docker restart web # stop then start in one step
docker rm web # remove a stopped container for good
run vs start vs restart
| docker run | Create a brand-new container from an image and start it |
|---|---|
| docker start | Start an existing, stopped container with its saved settings |
| docker restart | Stop and start an existing container in one step |
| docker rm | Delete a stopped container (data in named volumes survives) |
Sanity-check a docker run before you press enter
- -p has the host port on the left, container port on the right
- -v uses a named volume for data you want to keep
- Image tag is pinned to a real version, not 'latest'
- Secrets come from --env-file, not inline -e in shell history
- --rm only on throwaway runs, never on a service you want to keep
Where this leads
Once a docker run command grows past a few flags, you’re really describing a small configuration — and that’s exactly what Docker Compose is for. A long docker run becomes a few readable lines of YAML you can edit and commit. When you’re managing more than one container, that switch pays off fast; the Docker Compose beginner guide picks up right where this leaves off.
For now, the flags here — -d, -p, -v, -e, --name, --rm, and --restart — cover the large majority of what you’ll type day to day. Get comfortable with them and docker run stops being a magic incantation and becomes something you can read at a glance.