How to Run and Start Docker Containers

How to Run and Start Docker Containers

Reading time1 min
#Docker#Containers#DevOps#CommandLine

How to Run and Start Docker Containers

Two commands cover almost every case:

  • docker run IMAGE creates a new container from an image and starts it.
  • docker start CONTAINER starts 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

OptionWhat it does
-dRun in the background and print the container ID.
-itInteractive with a terminal, for shells and REPLs.
--rmRemove the container when it exits.
--name my-appFixed name for later commands. Must be unique.
-p 8080:80Publish container port 80 on host port 8080. -p 127.0.0.1:8080:80 limits it to localhost.
-e KEY=value, --env-file .envEnvironment variables.
-v my-data:/dataNamed volume for persistent data.
--mount type=bind,src="$(pwd)"/conf,dst=/etc/my-app,readonlyBind mount, more explicit than -v.
--restart unless-stoppedRestart after crashes and daemon restarts, unless stopped by hand.
--network my-netAttach to a user-defined network, where containers reach each other by name.
--memory 512m --cpus 1Resource limits.
-u 1000:1000Run as a specific UID and GID.
--entrypoint shReplace 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.04 exits at once: the default command is bash, and without a terminal attached it has nothing to read. Run a real long-running process, or use -it for a shell.
  • The app runs in the background. Services that daemonize themselves (nginx without daemon off, cron without -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-app confirms 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