Saltar a contenido

02 - Running Containers

What this session is

About 45 minutes. Deep dive on docker run - the flag that matters, what each does, how to think about a container's lifetime.

The mental model

A container is a wrapped process. docker run does three things:

  1. Creates a container from an image.
  2. Starts it (runs the configured command).
  3. Streams stdout/stderr to your terminal (or detaches if -d).

When the main process exits, the container stops. (A container is alive while its main process is alive.)

Anatomy of docker run

The full shape:

docker run [FLAGS] IMAGE [COMMAND] [ARGS]
  • FLAGS modify how the container runs (interactive, ports, volumes, env, etc.).
  • IMAGE is which image to use (e.g. nginx, python:3.12).
  • COMMAND (optional) overrides the image's default command.
  • ARGS (optional) are passed to that command.

Examples:

docker run nginx                                    # run nginx with its default cmd
docker run -it ubuntu bash                          # override: run bash instead
docker run python:3 python -c "print(42)"           # override + args

The flags that matter

-i / -t / -it - interactivity

  • -i keeps stdin open. Required if you want to type into the container.
  • -t allocates a pseudo-TTY. Makes the prompt look like a real terminal.
  • -it is the combination - for shells.
docker run -it --rm alpine sh        # interactive shell in Alpine

For one-off commands that don't need input, omit -it:

docker run --rm alpine date

--rm - auto-cleanup

Without --rm, the stopped container hangs around (so you can docker logs it, docker start it again, etc.). For one-shot runs, --rm is right.

-d - detached

Runs the container in the background. Returns to your shell immediately. Use for services (web servers, databases) you want to leave running.

docker run -d --rm --name web nginx

-p HOST:CONTAINER - port mapping

A container has its own network namespace. Its localhost is not your localhost. To reach a container's port from outside, publish it:

docker run -d --rm -p 8080:80 --name web nginx     # host:8080 → container:80

You can publish multiple ports:

docker run -p 8080:80 -p 8443:443 nginx

Or bind to a specific host interface:

docker run -p 127.0.0.1:8080:80 nginx              # only localhost on the host

If you don't -p a port, it's not reachable from your host. Containers on the same Docker network can still reach each other (page 07).

-e KEY=value - environment variables

docker run -e POSTGRES_PASSWORD=secret -d postgres

Many images are configured via env vars. Read the image's documentation on Docker Hub.

For long lists, use an env-file:

docker run --env-file my-env.txt my-image

--name NAME - give it a name

Without --name, Docker generates a random one (practical_einstein). With --name, you can refer to it by name in subsequent commands:

docker run -d --name mydb postgres
docker stop mydb
docker logs mydb

Names are unique per machine; only one container with a given name at a time.

-v HOST:CONTAINER - bind mounts and volumes

Mount a host directory or a named volume into the container. Full coverage in page 06; preview:

docker run -v $(pwd):/data alpine ls /data         # mount current dir as /data inside
docker run -v mydata:/var/lib/postgresql/data postgres   # named volume

--network NAME - network

Connects the container to a specific Docker network. Full coverage in page 07.

Resource limits

docker run --memory=512m --cpus=1.5 my-image

Limits memory and CPU. Containers without limits can starve the host. Always set limits for production-shaped runs.

--workdir DIR - initial working directory

docker run -it --workdir /app alpine sh

Equivalent to cd /app inside the container before the command runs.

--user UID:GID - run as a specific user

docker run --user 1000:1000 alpine id

Useful when working with mounted volumes (file ownership matches the host user).

Override the image's default command

docker run alpine echo "hi"          # alpine doesn't have echo as default; override

Everything after the image name and before the optional -- is the command + args.

Use --entrypoint to override the image's entrypoint (a deeper override; we'll see entrypoint vs cmd in page 05):

docker run --entrypoint sh alpine -c "echo hi"

Running interactively vs running detached

Two common shapes:

Interactive (foreground), one-shot:

docker run -it --rm IMAGE

Detached service:

docker run -d --rm --name SVC -p HOST:CONT IMAGE

For services you want to keep running, drop --rm (so the container survives a stop and can be restarted with docker start SVC).

A real example: a Redis instance

docker run -d --name redis -p 6379:6379 redis

That starts Redis on port 6379. Test from your host:

redis-cli ping          # if you have redis-cli installed
# or use a Python/Node client, or curl-like tools

Stop it:

docker stop redis

Restart later:

docker start redis

Remove (when done):

docker rm redis

A multi-line docker run (for readability)

Long commands are easier to read split across lines with \:

docker run -d \
  --name api \
  -p 8080:80 \
  -e DATABASE_URL=postgres://db:5432/mydb \
  -v $(pwd)/data:/data \
  --memory=512m \
  --restart=unless-stopped \
  my-image:1.0

This is what real-world docker run invocations look like. By page 08 (Compose) you'll see how to write this as YAML and avoid retyping the flags.

Exercise

  1. Hello with a name and port:

    docker run -d --rm --name web1 -p 8081:80 nginx
    
    Open http://localhost:8081. Stop the container.

  2. Environment variable:

    docker run --rm -e GREETING="hi from env" alpine sh -c 'echo $GREETING'
    
    Should print hi from env.

  3. Volume preview:

    docker run --rm -v $(pwd):/data alpine ls -la /data
    
    You should see the contents of your current directory listed.

  4. Resource limits:

    docker run --rm --memory=128m alpine free -h
    
    Note the memory limit reported inside.

  5. Use a name to re-attach:

    docker run -d --name persist alpine sleep 60
    docker logs persist
    docker exec -it persist sh
    exit
    docker stop persist
    docker rm persist
    

What you might wonder

"Why both -i and -t?" -i keeps stdin connected (you can type). -t makes Docker allocate a pseudo-terminal so the program thinks it's running in a real terminal (colors work, line editing works). For a shell you want both. For a pipe (e.g. echo "hi" | docker run -i ...) you only need -i.

"What's the difference between -p 8080:80 and -P (capital P)?" -P publishes all the image's EXPOSEd ports to random host ports. Rarely used in practice. Stick with explicit -p.

"What's --restart?" A policy for what to do when the container exits or the daemon restarts. --restart=always, --restart=unless-stopped, --restart=on-failure. Useful for services you want to survive reboots.

"Can a container have multiple processes?" Yes, but the convention is "one main process per container." If you need multiple, use multiple containers (page 08) or a process supervisor inside.

Done

  • Run interactive shells in containers.
  • Run detached services with port mappings.
  • Pass environment variables.
  • Name containers for easy reference.
  • Set resource limits.
  • Read a real-world docker run invocation.

Next: Images and tags →

Comments