How to Run and Start Docker Containers
Two commands cover almost every case:
docker run IMAGEcreates a new container from an image and starts it.docker start CONTAINERstarts an existing, stopped container again.
Mixing them up is the source of most confusion. Running docker run twice gives you two containers, and running docker start on an image name fails because there is no such container.
docker run
docker run [OPTIONS] IMAGE[:TAG] [COMMAND] [ARG...]
The smallest useful example:
docker run --rm hello-world
Docker pulls the image if needed, creates a container, starts it, prints its output and, because of --rm, removes it when it exits.
A web server in the background:
docker run -d --name web -p 8080:80 nginx:stable
curl -I http://localhost:8080
An interactive shell in a throwaway container:
docker run -it --rm ubuntu:24.04 bash
A one-off command, replacing the image's default command:
docker run --rm alpine:3 echo "hello from alpine"
docker run --rm -v "$(pwd)":/work -w /work node:22-alpine npm test
Order matters. Options go before the image name. Anything after the image name is the command for the container, so docker run nginx:stable -d uses -d as the container command instead of detaching.
Options worth knowing
| Option | What it does |
|---|---|
-d | Run in the background and print the container ID. |
-it | Interactive with a terminal, for shells and REPLs. |
--rm | Remove the container when it exits. |
--name my-app | Fixed name for later commands. Must be unique. |
-p 8080:80 | Publish container port 80 on host port 8080. -p 127.0.0.1:8080:80 limits it to localhost. |
-e KEY=value, --env-file .env | Environment variables. |
-v my-data:/data | Named volume for persistent data. |
--mount type=bind,src="$(pwd)"/conf,dst=/etc/my-app,readonly | Bind mount, more explicit than -v. |
--restart unless-stopped | Restart after crashes and daemon restarts, unless stopped by hand. |
--network my-net | Attach to a user-defined network, where containers reach each other by name. |
--memory 512m --cpus 1 | Resource limits. |
-u 1000:1000 | Run as a specific UID and GID. |
--entrypoint sh | Replace the image's entrypoint, handy for debugging. |
A realistic combination:
docker network create my-net
docker run -d --name my-db --network my-net \
-e POSTGRES_PASSWORD=secret -v my-db-data:/var/lib/postgresql/data \
postgres:17
docker run -d --name my-app --network my-net \
-p 127.0.0.1:3000:3000 \
-e DATABASE_URL=postgres://postgres:secret@my-db:5432/postgres \
--restart unless-stopped --memory 512m \
my-app:1.0
The app reaches the database as my-db because both containers are on the same user-defined network.
Starting an existing container
Containers stay on disk after they stop (unless they were run with --rm). Start one again with the same settings it was created with:
docker ps -a # list stopped containers too
docker start my-app
docker start my-db my-app # several at once
docker start runs in the background by default. To see the output, or to get back into an interactive container:
docker start -a my-job # attach to output, like a foreground run
docker start -ai sandbox # attach with stdin, for containers created with -it
Related commands:
docker stop my-app # SIGTERM, then SIGKILL after 10 seconds
docker stop -t 30 my-app # give the app 30 seconds to shut down
docker restart my-app # stop followed by start
docker kill my-app # SIGKILL immediately
If docker run --name my-app ... fails because the name is already in use, the old container still exists. Either start it with docker start my-app or remove it with docker rm -f my-app and run again. You need to remove and recreate whenever you want to change ports, environment variables or mounts: docker start always reuses the original settings.
Why a container exits right after it starts
A container lives exactly as long as its main process. If that process finishes, the container stops. Check what happened:
docker ps -a --filter name=my-app # STATUS shows "Exited (code)"
docker logs my-app
docker inspect -f '{{.State.ExitCode}} {{.State.Error}}' my-app
Common causes:
- Nothing to keep running.
docker run -d ubuntu:24.04exits at once: the default command isbash, and without a terminal attached it has nothing to read. Run a real long-running process, or use-itfor a shell. - The app runs in the background. Services that daemonize themselves (
nginxwithoutdaemon off,cronwithout-f) leave nothing in the foreground. Run them in foreground mode. - The app crashed. Exit code 1 or similar, with the reason in
docker logs. Often a missing environment variable or an unreachable database. - Exit code 137. The process was killed with SIGKILL, frequently by the out-of-memory killer.
docker inspect -f '{{.State.OOMKilled}}' my-appconfirms it.
To look inside an image that will not stay up, start it with a shell instead of its command:
docker run -it --rm --entrypoint sh my-app:1.0
Many containers: use Compose
Long docker run lines are hard to repeat correctly. The same setup in compose.yaml:
services:
my-db:
image: postgres:17
environment:
POSTGRES_PASSWORD: secret
volumes:
- my-db-data:/var/lib/postgresql/data
my-app:
image: my-app:1.0
ports:
- "127.0.0.1:3000:3000"
environment:
DATABASE_URL: postgres://postgres:secret@my-db:5432/postgres
depends_on:
- my-db
restart: unless-stopped
volumes:
my-db-data:
docker compose up -d # create and start everything
docker compose stop # stop without removing
docker compose start # start the existing containers again
docker compose down # stop and remove containers (volumes stay)
Quick reference
docker run -d --name NAME -p HOST:CONTAINER IMAGE:TAG # new container, background
docker run -it --rm IMAGE:TAG sh # throwaway shell
docker run --rm IMAGE:TAG COMMAND # one-off command
docker start NAME # existing container
docker start -ai NAME # existing interactive container
docker restart NAME / docker stop NAME
docker ps -a && docker logs NAME # why did it exit
