This gist might help you answer the following questions:
- How do I run a command inside a docker container?
- Why isn't my docker container running?
- Why is my docker container stopping right after I start it?
Most of the time when using docker, I want to get a shell inside a running container, either to see what's installed inside an image, or debug failing commands, or something else similar. I don't think this is the primary use case for the docker CLI, because it always takes a few different commands with flags that I always forget; before long, I find myself confused all over again as to how containers work and how to manipulate them with the CLI.
Here's the easiest CLI-only way I've found to pull an image, create a container from it, and run a terminal in that container. I've done my best to explain every command and flag so I'll never have to wonder again why my container seemed to stop immediately, or stop suddenly, or vanish off the face of the Earth.
For example, from DockerHub:
$ docker pull node:18
You can see the images you have pulled with:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
node 18 e390ceb99781 10 days ago 991MB
This is what the docker run
command does. So here's the quickest way to get a shell into a container to see what the image has:
$ docker run -it node:18 /bin/bash
-i
is interactive mode, which pipes everything correctly between the container and your own terminal stdout/stdin so you can actually see what the commands are doing. -t
actually "creates a terminal" in the docker container so you can run commands. The last two args are the image used, and the initial command to run in the container.
The above container will stop if you type exit
. You can see that it existed by doing docker ps -a
:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2354850cc1e5 node:18 "docker-entrypoint.s…" 4 minutes ago Exited (129) 3 minutes ago admiring_noether
$ docker create -it --name foo node:18 /bin/bash
This "creates" a container but doesn't start it. We can use --name
to give our container and friendly identifier, so we don't have to reference it by its hash ID every time. Since we haven't started it yet, this container won't show up with docker ps
, but will with docker ps -a
:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39dbbc0002133298ef3ccecf80daa934a707ace5d73995fd5406fbf6cbdc23de node:18 "docker-entrypoint.sh /bin/bash" About a minute ago Created foo
(Side note: you can add --no-trunc
to view any columns that docker might truncate by default, e.g. docker ps -a --no-trunc
lets you see the full COMMAND.)
It will also show up with docker container ls -a
.
Now if we run docker start foo
, we can see with docker ps
that our container is now running bash:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39dbbc0002133298ef3ccecf80daa934a707ace5d73995fd5406fbf6cbdc23de node:18 "docker-entrypoint.sh /bin/bash" 5 minutes ago Up 59 seconds foo
Note that this only happens because we specified -it
when creating the container. Without these options, the container would just exit immediately upon being started, because we didn't tell docker to forward IO streams or create a terminal.
Simply run:
$ docker attach foo
And that drops us into a shell in the container we started:
$ docker attach foo
root@39dbbc000213:/# node -v
v18.12.1
Now if we type exit
at this prompt, the container will stop entirely (it won't show up in docker ps
output). To detach from the container without stopping it, we can type ^P^Q
(Ctrl-P, Ctrl-Q):
root@39dbbc000213:/# <^P^Q> read escape sequence
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39dbbc000213 node:18 "docker-entrypoint.s…" 7 minutes ago Up 3 minutes foo
I need to confirm exactly what the behaviour is, but it seems like the container only remains running if there's at least one process executing inside of it. In this case we ran /bin/bash as our starting command, and attaching directly to the container and typing exit
in the bash terminal stops the bash process, which was the only one running in the container. Therefore, the container stops.
We use docker exec
:
$ docker exec -it foo /bin/bash
This drops us into a bash terminal in the already-running container (once again, the -it
is needed), which means we now have two bash processes running in the container: the initial one from when the container was started, and the one from our most recent exec. We can confirm this:
root@39dbbc000213:/# node -v
v18.12.1
root@39dbbc000213:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4164 3220 pts/0 Ss+ 22:59 0:00 /bin/bash
root 9 0.1 0.0 4164 3416 pts/1 Ss 23:08 0:00 /bin/bash
root 18 0.0 0.0 6760 2836 pts/1 R+ 23:08 0:00 ps aux
So now if we type exit
, the container stays alive because that only kills the second bash process (from our exec); the first bash process is still running:
root@39dbbc000213:/# exit
exit
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39dbbc000213 node:18 "docker-entrypoint.s…" 16 minutes ago Up 12 minutes foo
And now if we use attach
again, we can see there's only one bash process in the container:
$ docker attach foo
root@39dbbc000213:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4164 3336 pts/0 Ss 22:59 0:00 /bin/bash
root 19 0.0 0.0 6760 2820 pts/0 R+ 23:16 0:00 ps aux
And now, as expected, typing exit
stops the container entirely:
root@39dbbc000213:/# exit
exit
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39dbbc000213 node:18 "docker-entrypoint.s…" 21 minutes ago Exited (0) 5 seconds ago foo
This use case is for when the container exits immediately or soon after starting, and you want to try and figure out why. First determine the ID of the container that stopped with:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
39dbbc000213 node:18 "docker-entrypoint.s…" 21 minutes ago Exited (0) 5 seconds ago foo
The use the docker logs command to view logs from the container before it stopped:
docker logs 39dbbc000213
WIP section
See https://docs.docker.com/compose/gettingstarted/
Example docker-compose.yml file with a simple Flask + Redis application:
version: "3.9"
services:
web:
build: .
ports:
- "8000:5000"
volumes:
- .:/code
environment:
FLASK_DEBUG: "true"
redis:
image: "redis:alpine"
Start the services in docker-compose.yml:
docker compose up
Start the services in detached mode:
docker compose up -d
View running services:
docker compose ps
Run a command in a running service container:
docker compose run <service_name> <command>
e.g. to see environment variables in the container for a service "web"
docker compose run web env
Stop all service containers:
docker compose stop
Remove all service containers:
docker compose down
Remove all service containers including any volumes:
docker compose down --volumes
Rebuild image after changing a Dockerfile:
docker compose up --build
Networking in compose: https://docs.docker.com/compose/networking/