Short answer: docker run [OPTIONS] IMAGE [COMMAND] creates a new container from an image and starts it. The options you need almost every time are -d (run in the background), --name, -p HOST:CONTAINER to publish a port, -e to set environment variables, -v to mount a volume and --restart unless-stopped to bring it back after a reboot.
docker run -d --name web \
-p 127.0.0.1:8080:80 \
-e TZ=Europe/Amsterdam \
-v web-data:/usr/share/nginx/html \
--restart unless-stopped \
nginx:1.27Every option here is from the docker run reference on docs.docker.com. Docker was not run for this article, so the output blocks show the documented format, not a recorded session.
docker run syntax: options before the image
docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]Everything before the image name is an option for Docker; everything after it is the command for the container. That order is the most common docker run mistake: docker run nginx -p 8080:80 publishes nothing, because -p 8080:80 is handed to the container as its command. If the image is not on the machine yet, docker run pulls it first (Unable to find image 'nginx:1.27' locally is that, not an error); --pull always checks for a newer version every time. Leave the tag off and Docker uses :latest, which on a server you usually do not want: pin a version so a restart does not quietly change the software.
Cheat sheet: docker run options
| Option | What it does | Example |
|---|---|---|
-d | Run in the background, print the container ID | docker run -d nginx:1.27 |
--name | Name the container instead of a random one | --name web |
-p | Publish a container port on the host, HOST:CONTAINER | -p 127.0.0.1:8080:80 |
-P | Publish every EXPOSEd port on random host ports | docker run -d -P nginx:1.27 |
-e | Set an environment variable | -e TZ=Europe/Amsterdam |
--env-file | Read variables from a file | --env-file ./app.env |
-v | Mount a named volume or a host folder | -v pgdata:/var/lib/postgresql/data |
--mount | The same, in explicit key=value form | --mount type=bind,src=/srv/app,dst=/app,readonly |
--rm | Remove the container when it exits | docker run --rm alpine date |
-it | Interactive with a terminal, for shells | docker run --rm -it debian:12 bash |
--restart | Restart policy | --restart unless-stopped |
--entrypoint | Replace the image's ENTRYPOINT | --entrypoint sh |
-m, --memory | Hard memory limit | --memory 512m |
--cpus | CPU limit in cores | --cpus 1.5 |
--network | Attach to a network | --network appnet |
-w, -u | Working directory, user | -w /app -u 1000:1000 |
-d, --name, --rm and -it
docker run -d --name web nginx:1.27 # background, prints the container ID
docker logs -f web # follow what it writes
docker run --rm -it debian:12 bash # throwaway shell, deleted on exit
docker run --rm alpine cat /etc/os-release # one command, then goneWithout -d, the container runs in the foreground and Ctrl+C stops it. With -it and a shell, Ctrl+P then Ctrl+Q detaches and leaves it running. A name is unique per host, so the second docker run --name web fails:
docker: Error response from daemon: Conflict. The container name "/web" is already in use by container "4f1c2e9a7b3d...". You have to remove (or rename) that container to be able to reuse that name.Remove the old one with docker rm -f web, or start the existing container with docker start web; the Docker commands cheat sheet lists the rest of the container lifecycle. Use -t only where a terminal exists: in cron or CI, -it stops with the input device is not a TTY, and plain -i or nothing at all is right.
Publish ports with -p
| Form | Meaning |
|---|---|
-p 8080:80 | Host port 8080 to container port 80, on every address of the host |
-p 127.0.0.1:8080:80 | Only on loopback: reachable from the server itself, for a reverse proxy |
-p 203.0.113.10:443:443 | Only on one public address of the host |
-p 51820:51820/udp | A UDP port (TCP is the default) |
-p 80 | Container port 80 on a random free host port; docker port shows which |
-p 8000-8010:8000-8010 | A range, mapped one to one |
The host side always comes first. docker port web lists what is published, and docker ps shows it in the PORTS column. A port is fixed when the container is created: to change it, remove the container and run it again with the new -p. Two errors cover almost every port problem:
docker: Error response from daemon: driver failed programming external connectivity on endpoint web (...): Bind for 0.0.0.0:8080 failed: port is already allocated.... Error starting userland proxy: listen tcp4 0.0.0.0:80: bind: address already in use.For the first, docker ps shows which container holds the port. For the second, Address already in use finds the host process.
Environment variables: -e and --env-file
docker run -d --name db \
-e POSTGRES_PASSWORD=change-me \
-e TZ=Europe/Amsterdam \
postgres:17
export API_TOKEN=abc123
docker run --rm -e API_TOKEN alpine env | grep API_TOKEN # value taken from your shell
docker run -d --name app --env-file ./app.env myapp:1.4-e NAME=value sets a variable; -e NAME alone copies it from your shell, and leaves it unset in the container if your shell does not have it. An env file holds one NAME=value per line and lines starting with # are comments:
# database
DB_HOST=db
DB_USER=app
DB_PASSWORD=s3cret
# NOT like this: the quotes become part of the value
GREETING="hello"Volumes and bind mounts: -v and --mount
docker run -d -v pgdata:/var/lib/postgresql/data postgres:17 # named volume
docker run -d -v /srv/site:/usr/share/nginx/html:ro nginx:1.27 # host folder, read-only
docker run -d --mount type=bind,src=/srv/site,dst=/usr/share/nginx/html,readonly nginx:1.27A name before the colon is a named volume that Docker manages; a path starting with / is a bind mount of that host folder. Both keep data when the container is removed. The two syntaxes differ in one way that matters: when the host folder does not exist, -v creates it (as an empty folder owned by root), while --mount stops with bind source path does not exist, which catches typos. A bare -v /data with no name creates an anonymous volume, which is easy to lose with a prune.
Restart policies: --restart
| Policy | Restarts after a crash | After a reboot or daemon restart |
|---|---|---|
no | No (the default) | No |
on-failure[:N] | Only on a non-zero exit, at most N times if given | Yes, if it was running |
always | Yes | Yes, even after you stopped it by hand |
unless-stopped | Yes | Yes, unless you stopped it by hand |
unless-stopped is the usual choice for a service on a server. Change the policy of an existing container with docker update --restart unless-stopped web. Docker refuses to combine --rm with a restart policy, since one deletes the container on exit and the other wants to start it again.
Override the entrypoint and the command
An image defines what runs in two parts: ENTRYPOINT, the program, and CMD, its default arguments (or the whole command when there is no entrypoint). Check both before overriding either:
docker image inspect -f '{{json .Config.Entrypoint}} {{json .Config.Cmd}}' nginx:1.27["/docker-entrypoint.sh"] ["nginx","-g","daemon off;"]| You run | What runs in the container |
|---|---|
docker run nginx:1.27 | ENTRYPOINT + CMD: /docker-entrypoint.sh nginx -g 'daemon off;' |
docker run nginx:1.27 nginx -T | Arguments replace CMD: /docker-entrypoint.sh nginx -T |
docker run --entrypoint sh nginx:1.27 | Just sh: --entrypoint replaces ENTRYPOINT and clears CMD |
docker run --entrypoint cat nginx:1.27 /etc/nginx/nginx.conf | cat /etc/nginx/nginx.conf: arguments go to the new entrypoint |
docker run --rm -it --entrypoint sh nginx:1.27 # a shell instead of the server
docker run --rm --entrypoint ls nginx:1.27 -la /etc/nginx # one command with arguments
docker run --rm postgres:17 postgres --version # replace CMD only--entrypoint takes only the program; its arguments go after the image name, not inside the quotes of --entrypoint. Overriding the entrypoint is the standard way to look inside an image whose own entrypoint exits straight away. One catch: an image whose ENTRYPOINT is written in shell form (a plain string, not a JSON list) ignores CMD and any arguments you pass, so there only --entrypoint changes anything.
Memory and CPU limits
docker run -d --name worker --memory 512m --memory-swap 512m --cpus 1.5 myapp:1.4
docker stats --no-stream--memory is a hard limit: a container that goes over it is killed by the kernel’s OOM killer and exits with code 137. Setting --memory-swap to the same value means no swap on top. --cpus 1.5 caps it at one and a half cores’ worth of CPU time. Without limits, one container can take the whole server, which on a small VPS takes every other service down with it. If containers die with 137, OOM-killed processes explains how to confirm it.
Networks
docker network create appnet
docker run -d --name db --network appnet -e POSTGRES_PASSWORD=change-me postgres:17
docker run -d --name app --network appnet -p 127.0.0.1:3000:3000 -e DB_HOST=db myapp:1.4On a network you create, containers reach each other by name (db above), with no published port needed for the database. The default bridge network does not offer that name lookup. --network host puts the container straight on the host’s network: no isolation, and -p is ignored with WARNING: Published ports are discarded when using host network mode.
docker run vs start vs exec vs create
| Command | What it does | Use it to |
|---|---|---|
docker run | Creates a new container from an image and starts it | Start something for the first time, or after changing options |
docker create | Creates the container but does not start it | Prepare a container to start later |
docker start | Starts an existing, stopped container with its original options | Bring back a container you stopped |
docker exec | Runs an extra command inside a running container | Open a shell or run a one-off command: docker exec -it web sh |
Each docker run makes a new container, so running the same line twice gives two containers (or a name conflict). To change options such as ports, variables or limits, remove the container and run it again; data in volumes and bind mounts carries over.
The same in Docker Compose
Once a docker run line grows past a few options, a compose.yaml is easier to keep. The example at the top of this page, with the memory and CPU limits from above added, becomes:
services:
web:
image: nginx:1.27
ports:
- "127.0.0.1:8080:80"
environment:
TZ: Europe/Amsterdam
# env_file: ./app.env
volumes:
- web-data:/usr/share/nginx/html
restart: unless-stopped
mem_limit: 512m
cpus: 1.5
# entrypoint: ["sh"]
# command: ["nginx", "-T"]
volumes:
web-data:docker compose up -d in that folder starts it. A complete example with a database, a reverse proxy and persistent volumes is self-hosting n8n with Docker Compose.
docker run errors and fixes
| Message | Cause | Fix |
|---|---|---|
The container name "/web" is already in use | A container with that name exists, maybe stopped | docker start web, or docker rm -f web and run again |
port is already allocated | Another container publishes the same host port | Pick another host port, or stop that container |
bind: address already in use | A host program listens on that port | Find it with ss -ltnp, or use another port |
the input device is not a TTY | -t in cron, CI or a pipe | Drop -t (keep -i if you pipe input) |
bind source path does not exist | --mount type=bind with a missing host folder | Create the folder or fix the path |
pull access denied ... repository does not exist or may require 'docker login' | Typo in the image name, or a private image | Check the name; docker login for private registries |
Exited (137) | Killed with SIGKILL: out of memory, docker kill, or a docker stop that ran out of time | Check docker inspect -f '{{.State.OOMKilled}}' NAME; raise --memory or find the leak |
permission denied while trying to connect to the Docker daemon socket | Your user is not in the docker group | Add it, or use sudo |
For the last one, see permission denied on the Docker daemon socket; if docker is not installed at all, start with the Docker install guide.
Frequently asked
What does -p do in docker run?
-p HOST:CONTAINER publishes a container port on the host: -p 8080:80 makes the container's port 80 reachable on port 8080 of every host address. -p 127.0.0.1:8080:80 limits it to the server itself, which is what you want behind a reverse proxy, because published ports bypass ufw rules.
How do I pass environment variables to docker run?
Use -e NAME=value for each variable, -e NAME to copy the value from your shell, or --env-file FILE for a file with one NAME=value per line. The env file is read literally: quotes become part of the value and variables are not expanded. docker exec CONTAINER env shows what the container received.
How do I override the entrypoint in docker run?
Put --entrypoint before the image name, for example docker run --rm -it --entrypoint sh nginx:1.27. It replaces the image's ENTRYPOINT and clears its CMD; arguments for the new entrypoint go after the image name, as in docker run --rm --entrypoint ls nginx:1.27 -la /etc/nginx.
What is the difference between docker run and docker start?
docker run creates a new container from an image and starts it with the options you give. docker start starts an existing, stopped container again with the options it was created with. To change ports, variables or limits, remove the container and run it again; data in volumes is kept.