Contents
Docker Run Tutorial: Essential Flags and Real-World Examples
docker run is Docker's most commonly used command, and it has more flags than any other command. Its basic syntax is:
docker run [flags] IMAGE[:tag] [COMMAND] [COMMAND_ARGS]
The part after the image name replaces the image's default command (CMD):
$ docker run --rm alpine:3.23 cat /etc/alpine-release
3.23.6
--rm is a Docker flag. cat /etc/alpine-release is the command that runs inside the container, replacing Alpine's default /bin/sh.
Always put flags before the image name
Everything after the image name is treated as a command to run inside the container:
$ docker run alpine:3.23 --rm echo hi
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: error during container init: exec: "--rm": executable file not found in $PATH
Run 'docker run --help' for more information
Docker tries to run a program named --rm inside the container and, unsurprisingly, can't find it (executable file not found). When you see this error, check the flag placement first.
Run modes
| Flag | Description |
|---|---|
-d, --detach | Run in the background and return immediately after printing the container ID |
-i, --interactive | Keep standard input open |
-t, --tty | Allocate a pseudo-terminal |
--rm | Automatically remove the container when it exits |
--name | Set the container name; otherwise, Docker generates a random one (for example, dreamy_solomon) |
docker run -it --rm ubuntu:26.04 bash # Open an interactive shell and remove the container when you exit
docker run -d --name cache redis:8-alpine # Run a background service
-it are almost always used together to open an interactive shell. With -i alone, you can pipe data into a container:
$ echo "hello" | docker run -i --rm alpine:3.23 cat
hello
Ports: -p
The syntax is -p HOST_PORT:CONTAINER_PORT. Here are three forms in action:
$ docker run -d --name p1 -p 8080:80 nginx:1.30
27b89a4fae92dfe05bf051e11700033c7b9bb71f8747acc986d2a535cc1baa00
$ docker run -d --name p2 -p 127.0.0.1:8081:80 nginx:1.30
3327f3c1df4fa1e03fa3a120e5d889859fc0cb53d0aaf269826c81b25f4411c2
$ docker run -d --name p3 -P nginx:1.30
57c9a309c4758786361738b7b9a4231f404cb9bbab28f38cad39b4b6e996d9e0
$ docker port p1
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080
$ docker port p2
80/tcp -> 127.0.0.1:8081
$ docker port p3
80/tcp -> 0.0.0.0:32768
80/tcp -> [::]:32768
| Form | Result |
|---|---|
-p 8080:80 | Binds to all network interfaces (0.0.0.0 and IPv6 [::]), so other computers can connect |
-p 127.0.0.1:8081:80 | Binds only to the local machine, so only this computer can connect |
-P | Maps ports declared by the image with EXPOSE to random host ports (this time, port 32768) |
Run docker port CONTAINER_NAME to see the actual mappings. For a UDP port, use -p 5353:53/udp.
On a cloud host,
-p 8080:80means external clients can connect directly. Docker also modifies firewall rules, so ufw settings might not block access. For services such as databases that should not be exposed externally, bind to127.0.0.1or omit-pand use a custom network to connect containers.
Data: -v and --mount
-v HOST_PATH:CONTAINER_PATH mounts a host directory into a container (a bind mount). Create a site directory containing an index.html file:
$ ls site
index.html
$ docker run -d --name site -p 8080:80 -v "$(pwd)/site:/usr/share/nginx/html:ro" nginx:1.30
e91f46b91ff8f0f64d59d6605bfddea0ee5bca809de86d81cbd5051815e65dd1
$ curl -s http://localhost:8080 | head -n 3
<!doctype html>
<html><head><meta charset="utf-8"><title>My site</title>
<style>body{font-family:system-ui,sans-serif;max-width:640px;margin:60px auto;padding:0 16px;color:#1f2937}h1{color:#1d63ed}</style></head>
Windows equivalents (select a tab to switch):
docker run -d --name site -p 8080:80 -v "$(pwd)/site:/usr/share/nginx/html:ro" nginx:1.30
docker run -d --name site -p 8080:80 -v "${PWD}/site:/usr/share/nginx/html:ro" nginx:1.30
docker run -d --name site -p 8080:80 -v "%cd%/site:/usr/share/nginx/html:ro" nginx:1.30

The trailing :ro means read-only. Attempts to write to this directory from inside the container will be denied:
$ docker exec site sh -c "echo test > /usr/share/nginx/html/x.txt"
sh: 1: cannot create /usr/share/nginx/html/x.txt: Read-only file system
Another option is a Docker-managed Volume (-v NAME:CONTAINER_PATH), which is suitable for storing data such as database files:
docker run -d -v pgdata:/var/lib/postgresql postgres:18
--mount is a more explicit, but longer, way to write a mount. See Volumes and Bind Mounts to learn about the differences and when to use each.
Environment variables: -e and --env-file
$ docker run --rm -e GREETING=hello -e APP_ENV=dev alpine:3.23 env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=18f035817c43
GREETING=hello
APP_ENV=dev
HOME=/root
If you specify -e VARIABLE_NAME without a value, Docker uses the host's current environment variable with the same name:
$ APP_ENV=prod docker run --rm -e APP_ENV alpine:3.23 sh -c 'echo $APP_ENV'
prod
When you have many variables, use --env-file FILE instead. See Environment Variables and .env Files for details.
Networks
Containers on the same custom network can connect to each other by container name:
$ docker network create mynet
2ecd542737519b0b8919061077814e007a72e2d07973f0840608b1b5454c678e
$ docker run -d --name myweb --network mynet nginx:1.30
9a6ca3dca3a003e28b9870f3eec436cf7023a6872e8ddd4cee971d29f845583b
$ docker run --rm --network mynet alpine:3.23 ping -c 2 myweb
PING myweb (172.19.0.2): 56 data bytes
64 bytes from 172.19.0.2: seq=0 ttl=64 time=0.094 ms
64 bytes from 172.19.0.2: seq=1 ttl=64 time=0.064 ms
--- myweb ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.064/0.079/0.094 ms
$ docker run --rm alpine:3.23 ping -c 1 -W 2 myweb
ping: bad address 'myweb'
- The first ping container is on
mynet, where Docker's built-in DNS resolvesmywebto172.19.0.2. - The second container has no
--networkflag, so it uses the default network and cannot findmyweb(bad address).
Avoid container names that are also top-level domains (TLDs), such as
weborapp. When containers aren't on the same network, external DNS may resolve these names to an unexpected address (for example,127.0.53.53), making troubleshooting more confusing.
docker run --network host nginx:1.30 # Use the host's network directly (Linux)
docker run --network none alpine:3.23 # Disable networking entirely
See Networking and Container Connectivity for details.
Restart policies
docker run -d --restart unless-stopped nginx:1.30
Available values are no, on-failure, always, and unless-stopped. See Container Lifecycle for how they work.
Resource limits
$ docker run -d --name limited --memory 64m --cpus 0.5 nginx:1.30
2e65ce89f346cb7d9d57f1e6ad529d63ad53d1d914b6d3178dc3344a1d3325f0
$ docker stats --no-stream limited
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS
2e65ce89f346 limited 0.00% 3.922MiB / 64MiB 6.13% 736B / 126B 1.77MB / 8.19kB 2
MEM USAGE / LIMIT shows 3.922MiB / 64MiB, confirming that the memory limit is in effect. When idle, nginx uses less than 4 MB.
| Flag | Description |
|---|---|
--memory, -m | Memory limit, such as 512m or 2g; if exceeded, the process is killed (OOM, exit code 137) |
--memory-swap | Combined limit for memory and swap |
--cpus | Number of CPUs available; can be a decimal, so 0.5 means half a CPU |
--pids-limit | Maximum number of processes in the container |
For a real example of exceeding the memory limit, see the OOM example in Viewing Logs and Troubleshooting.
Override image defaults
$ docker run --rm --entrypoint sh nginx:1.30 -c "nginx -v; whoami"
nginx version: nginx/1.30.5
root
$ docker run --rm -u 1000:1000 alpine:3.23 id
uid=1000 gid=1000 groups=1000
$ docker run --rm -w /tmp alpine:3.23 pwd
/tmp
$ docker run --rm -h myhost alpine:3.23 hostname
myhost
| Flag | Description |
|---|---|
--entrypoint | Override ENTRYPOINT; often used to inspect an image that would otherwise start a service immediately |
-u, --user | Run as the specified user (UID:GID) |
-w, --workdir | Override the working directory |
-h, --hostname | Set the container's hostname |
Use -u to make files created by a container belong to you instead of root:
docker run --rm -u "$(id -u):$(id -g)" -v "$(pwd):/work" -w /work node:24-alpine npm install
Read-only file system
$ docker run --rm --read-only alpine:3.23 touch /hello
touch: /hello: Read-only file system
--read-only makes the container's entire file system read-only, so even a compromised application can't modify files. Allow writes where needed with --tmpfs /tmp or a Volume. See Security Best Practices.
Other useful flags
| Flag | Description |
|---|---|
--init | Use a small init process as PID 1 to forward signals correctly (docker stop won't need to wait 10 seconds; see Container Lifecycle) |
--platform | Specify a platform, such as linux/amd64 |
--add-host | Add an entry to the hosts file, such as --add-host host.docker.internal:host-gateway |
--label | Add a label to make filtering easier |
--pull always | Pull the image again before each run |
--log-opt max-size=10m | Limit the log file size |
Quick reference to more flags
This section covers useful flags not explained in detail in this chapter, based on docker run --help (Docker 29). Run docker run --help for the full list.
| Category | Flag | Description |
|---|---|---|
| Security | --cap-add / --cap-drop | Add or remove Linux capabilities; see Security Best Practices for a hands-on example |
| Security | --security-opt | Security options, such as no-new-privileges and seccomp settings |
| Security | --privileged | Give the container all privileges, nearly disabling isolation; avoid using it |
| Security | --userns | Select the user namespace |
| Health checks | --health-cmd | Health check command, such as --health-cmd "curl -f http://localhost/" |
| Health checks | --health-interval / --health-timeout / --health-retries | Check interval, timeout, and number of consecutive failures before marking a container unhealthy |
| Health checks | --health-start-period / --health-start-interval | Grace period and check interval during startup |
| Health checks | --no-healthcheck | Disable the HEALTHCHECK set in the image |
| Networking | --network-alias | Add another name on the network; see docker network |
| Networking | --ip | Assign a static IP on a custom network |
| Networking | --dns / --dns-search | Set custom DNS servers and search domains |
| Networking | --expose | Declare a port without mapping it to the host; same effect as Dockerfile EXPOSE |
| Networking | -P, --publish-all | Map all declared ports to random host ports |
| Stopping | --stop-signal | Signal sent by docker stop; defaults to SIGTERM |
| Stopping | --stop-timeout | Number of seconds docker stop waits; defaults to 10 |
| Resources | --cpuset-cpus | Use only specified CPUs, such as 0,1 or 0-3 |
| Resources | -c, --cpu-shares | Relative CPU weight (applies only when resources are contested) |
| Resources | --memory-reservation | Soft memory limit |
| Resources | --shm-size | Size of /dev/shm; applications such as PostgreSQL and browsers often need a larger value |
| Resources | --ulimit | For example, --ulimit nofile=65535:65535 adjusts the open-file limit |
| Resources | --oom-kill-disable | Disable killing a process when memory runs low (must be used with --memory) |
| Devices | --device | Pass a host device to the container, such as /dev/ttyUSB0 |
| Devices | --gpus | Use GPUs, such as --gpus all (requires NVIDIA Container Toolkit) |
| Namespaces | --pid host / --ipc / --uts | Share namespaces with the host or another container; see Under the Hood for how this works |
| System | --sysctl | Set kernel parameters for the container, such as net.core.somaxconn=1024 |
| System | --umask | Set the umask for processes in the container |
| Mounts | --volumes-from | Mount all Volumes from another container |
| Other | --cidfile | Write the container ID to a file for use in scripts |
| Other | --label-file | Read labels from a file |
| Other | --log-driver | Log driver, such as json-file, local, or syslog |
| Other | -q, --quiet | Hide progress when pulling an image |
| Other | --use-api-socket | Mount the Docker API socket and login credentials into the container (equivalent to host root access; see Security Best Practices) |
Common combinations
# 1. One-off utility container: run a command in the current directory
docker run --rm -v "$(pwd):/data" -w /data alpine:3.23 ls -la
# 2. Local development database, accessible only from the local machine
docker run -d --name devdb --restart unless-stopped \
-p 127.0.0.1:5432:5432 \
-e POSTGRES_PASSWORD=dev \
-v devdb:/var/lib/postgresql \
postgres:18
# 3. Production service with resource and log size limits
docker run -d --name api --restart unless-stopped \
--memory 1g --cpus 2 \
--log-opt max-size=10m --log-opt max-file=3 \
--env-file .env -p 3000:3000 \
myorg/api:1.4.2
# 1. One-off utility container: run a command in the current directory
docker run --rm -v "${PWD}:/data" -w /data alpine:3.23 ls -la
# 2. Local development database, accessible only from the local machine
docker run -d --name devdb --restart unless-stopped `
-p 127.0.0.1:5432:5432 `
-e POSTGRES_PASSWORD=dev `
-v devdb:/var/lib/postgresql `
postgres:18
# 3. Production service with resource and log size limits
docker run -d --name api --restart unless-stopped `
--memory 1g --cpus 2 `
--log-opt max-size=10m --log-opt max-file=3 `
--env-file .env -p 3000:3000 `
myorg/api:1.4.2
# 1. One-off utility container: run a command in the current directory
docker run --rm -v "%cd%:/data" -w /data alpine:3.23 ls -la
# 2. Local development database, accessible only from the local machine
docker run -d --name devdb --restart unless-stopped ^
-p 127.0.0.1:5432:5432 ^
-e POSTGRES_PASSWORD=dev ^
-v devdb:/var/lib/postgresql ^
postgres:18
# 3. Production service with resource and log size limits
docker run -d --name api --restart unless-stopped ^
--memory 1g --cpus 2 ^
--log-opt max-size=10m --log-opt max-file=3 ^
--env-file .env -p 3000:3000 ^
myorg/api:1.4.2
Commands get hard to maintain as the number of flags grows. For long-running services or setups with multiple containers, use Docker Compose and put the configuration in a file.
Recap
- Put flags before the image name; anything after it is a command to run inside the container.
-d,--rm,--name,-p,-v, and-eare the most common flags.--memory,--cpus, and--pids-limitlimit resources.--entrypoint,-u, and-woverride image defaults.
Exercises
Exercise 1: Write a command to run nginx:1.30-alpine in the background, name it site, allow connections to port 8080 only from the local machine, set a 128 MB memory limit, and remove the container when it exits.
Answer
docker run -d --rm --name site -p 127.0.0.1:8080:80 --memory 128m nginx:1.30-alpine
--rm automatically removes the container after it stops.
Exercise 2: The postgres:18 image's ENTRYPOINT starts the database. How can you open its shell without starting the database?
Answer
docker run --rm -it --entrypoint sh postgres:18
--entrypoint overrides the image's ENTRYPOINT.
FAQ
Why does a flag have no effect when I put it after the image name?
Everything after the image name is treated as a command to run inside the container. You must put docker run flags before the image name.
What's the difference between -p 8080:80 and -p 127.0.0.1:8080:80?
The first binds to all network interfaces, so other computers can connect. The second binds only to the local machine. For services such as databases that should not be exposed externally, use the second form or omit -p entirely.
How many flags does docker run have?
Docker 29's docker run --help lists about 100 flags. This chapter covers the most common ones, and the final Quick reference to more flags lists other useful options.
Updated October 14, 2026