Skip to main content

How to run a container from a Docker image?

docker run is the most-used command in the entire Docker CLI. It pulls the image if needed, creates a container, and starts it — all in one shot. Knowing the half-dozen flags that show up in every production command is most of what "using Docker" actually is.

Theory

TL;DR

  • Anatomy: docker run [FLAGS] IMAGE[:TAG] [COMMAND] [ARGS...]
  • Image goes between flags and any command override. Everything after the image replaces the image's CMD.
  • Six flags cover 90% of cases: -d, --name, -p, -v, -e, --rm.
  • docker run = docker pull (if missing) + docker create + docker start. Three operations in one command.
  • Use --rm for one-off commands (auto-cleanup on exit). Use -d + --restart for long-lived services.

Quick example

bash
# Minimum: pull and run $ docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world Digest: sha256:9f8e7d6c... Status: Downloaded newer image for hello-world:latest Hello from Docker!

The daemon noticed the image was missing, pulled it from Docker Hub, then ran it. One command, three operations.

The six flags you will use every day

-d / --detach — run in the background

bash
docker run -d nginx:1.27-alpine # Returns immediately with the container ID; container keeps running.

Without -d, the CLI streams logs and exits when the container does. With -d, you get back to your shell instantly.

--name — give it a memorable name

bash
docker run -d --name web nginx:1.27-alpine # Now you can do: docker stop web, docker logs web, etc. # Without --name, Docker assigns a random name like "sleepy_einstein".

-p / --publish — expose ports

bash
# Format: HOST_PORT:CONTAINER_PORT docker run -d -p 8080:80 nginx:1.27-alpine # Container's port 80 is reachable at http://localhost:8080 # Bind to specific host interface docker run -d -p 127.0.0.1:8080:80 nginx:1.27-alpine # Only loopback, not the public network # Random host port (auto-assigned) docker run -d -p 80 nginx:1.27-alpine # Run docker port <name> to find the host port that was picked

Without -p, the container is isolated on its own network — reachable from other containers on the same network, but not from the host.

-v / --volume — mount storage

bash
# Named volume docker run -d -v pgdata:/var/lib/postgresql/data postgres:16 # Bind mount (specific host path) docker run -d -v /home/me/site:/usr/share/nginx/html nginx:1.27-alpine # Read-only mount docker run -d -v ./conf:/etc/myapp:ro myapp

Use named volumes for persistent state, bind mounts for live-reload dev or specific host paths.

-e / --env — set environment variables

bash
# Single var docker run -e POSTGRES_PASSWORD=devpass postgres:16 # Pass through from your shell docker run -e API_KEY postgres:16 # From a file docker run --env-file .env postgres:16

Do NOT pass secrets via -e in production — they show up in docker inspect, image history, and process listings. Use Swarm secrets, BuildKit secret mounts, or external secret managers.

--rm — auto-cleanup on exit

bash
# One-off command, container vanishes when done docker run --rm -it alpine sh # When you exit the shell, the container is removed (no orphan).

Makes ad-hoc commands feel like running local scripts. Without --rm, every exited container piles up until you docker rm them.

Long-lived service flags

bash
--restart=no # default: do not restart --restart=on-failure # restart only if exit code != 0 --restart=always # restart always (even on docker daemon restart) --restart=unless-stopped # like 'always', but respects manual docker stop

For production services, --restart=unless-stopped is the usual choice.

Putting it all together: a realistic command

bash
docker run -d \ --name api \ --restart=unless-stopped \ -p 3000:3000 \ -v api_uploads:/app/uploads \ -v ./api/.env:/app/.env:ro \ -e NODE_ENV=production \ --memory=512m \ --cpus=0.5 \ --health-cmd='curl -f http://localhost:3000/health' \ --health-interval=30s \ myapp:1.0

This is essentially what a production single-host deploy looks like before you graduate to Compose, Swarm, or Kubernetes.

Overriding the image's CMD

Anything after the image name replaces the image's default command:

bash
# Image's default CMD (probably the service) docker run nginx:1.27-alpine # Override CMD with a one-off command docker run nginx:1.27-alpine sh -c 'nginx -V' # Runs nginx -V then exits, instead of starting the daemon. # Drop into a shell for debugging docker run -it nginx:1.27-alpine sh

If the image has an ENTRYPOINT, your trailing args become its arguments instead. Use --entrypoint /bin/sh to fully override an entrypoint when debugging.

Common mistakes

Forgetting -d for a service

bash
$ docker run nginx:1.27-alpine # Terminal hangs streaming nginx logs. # Ctrl+C stops the container too.

For anything other than a one-off command, you usually want -d.

Wrong port direction in -p

bash
# WRONG: thinks 80 is on the host, 8080 in container docker run -p 80:8080 nginx:1.27-alpine # Nothing serves on container port 8080; you get connection refused on host:80. # RIGHT: HOST:CONTAINER docker run -p 8080:80 nginx:1.27-alpine

The order is host first, container second. Common gotcha.

Putting flags after the image name

bash
# WRONG: -p is treated as an argument to nginx docker run nginx:1.27-alpine -p 8080:80 # RIGHT: flags before image docker run -p 8080:80 nginx:1.27-alpine

Docker stops parsing flags at the image name. Anything after is the command for the container, not for docker run.

Running interactive without -it

bash
# WRONG: shell exits immediately because there is no TTY docker run alpine sh # RIGHT: -i keeps stdin open, -t allocates a TTY docker run -it alpine sh

-it (or -i -t) is the magic incantation for interactive sessions.

Real-world usage

  • Local development: docker run --rm -it -v $PWD:/work node:22 npm test — disposable container, mounts current dir, runs tests, vanishes.
  • Staging/single-host deploy: the long docker run -d --name api --restart=unless-stopped ... form, often wrapped in a shell script or Compose file.
  • CI/CD: docker run --rm myapp:test npm run test produces an exit code that the CI uses for pass/fail.
  • One-off DB migrations: docker run --rm --network mynet -e DATABASE_URL=... migrator:1.0 runs and disappears.

Follow-up questions

Q: What is the difference between docker run and docker create?


A: create allocates the container but does not start it; you have to docker start after. run does both. create is useful when you want to set up the container and start it later, or look at its config (docker inspect) before starting.

Q: How do I run a command in an existing container instead of starting a new one?


A: docker exec. docker run always creates a new container; docker exec runs a command in a running one. For a shell into an existing container: docker exec -it <name> sh.

Q: Why is my interactive docker run -it exiting immediately?


A: Probably because the image's ENTRYPOINT or CMD is not a shell. Try docker run -it --entrypoint sh <image> to force a shell. Or check the image's docs for the right command to run.

Q: Can I run a container without giving it a name?


A: Yes — Docker generates a random one (musing_einstein, crazy_curie). Fine for one-off --rm commands. For anything you will reference later (logs, exec, stop), --name saves you typing.

Q: (Senior) Why might you avoid docker run in production and use Compose or Kubernetes instead?


A: A long docker run command is imperative state living in someone's terminal history. Compose makes it declarative (in compose.yaml), version-controllable, and reproducible. Kubernetes goes further (multi-host, self-healing, rolling updates). For a single throwaway service, docker run is fine. For anything you will redeploy, declarative beats imperative.

Examples

Local one-off: run a quick test

bash
$ docker run --rm -it \ -v $PWD:/work \ -w /work \ node:22-alpine \ npm test # Tests run inside the container, output streams to your terminal, # container removed when done. Your laptop stays clean.

--rm, -it, -v $PWD:/work, -w /work: the four flags that make a container behave like a local script.

Production-style web service

bash
$ docker run -d \ --name web \ --restart=unless-stopped \ -p 80:80 -p 443:443 \ -v web_certs:/etc/nginx/certs \ -v ./nginx.conf:/etc/nginx/nginx.conf:ro \ --memory=256m \ nginx:1.27-alpine $ docker ps --filter name=web CONTAINER ID IMAGE STATUS PORTS NAMES f7c2a9e1b8d4 nginx:1.27-alpine Up 3 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp web

Detached, named, restart-on-reboot, two ports published, certs in a named volume, config bind-mounted read-only, memory cap.

Override the image's CMD for debugging

bash
# Image's default CMD starts postgres. We want a shell. $ docker run --rm -it postgres:16 bash # Or skip the entrypoint entirely $ docker run --rm -it --entrypoint sh postgres:16

Useful when an image's startup is failing and you need to poke around inside.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Comments

No comments yet