Understanding the basics of docker and docker commands is the first step into app containerization.
Docker
, Dockerfile
, docker-compose
... When you first get into the docker world it can be a bit daunting to understand how all of these tools work and relate to each other.
In this post, I'll be playing around with a simple ruby container just using docker commands. No Dockerfile
nor docker-compose
for now. Just the basic docker commands.
It sure helped me really grasp the basic concepts behind images and containers in isolation before moving into more complex concepts.
Images vs Containers
In docker, it all begins with an image. The image sets the minimum base for a container to run. An image is an immutable file that will serve as a snapshot for the container, and the container will be the actual process on which you can work.
It might help to look at it with an OOP mindset - think of an image as a class and the container as an instance of that class. A container is an instance of an image.
For the purpose of this exercise, I want to run a simple ruby sandbox with irb
. This sandbox will be available in a container but for this container to run I'll need its snapshot - the ruby image.
Luckily, we don't have to build the ruby image ourselves. There are plenty of ready-made images available on Docker hub that you can use. If you search for ruby
you'll find the official ruby image in different versions.
The docker run
command
The command that starts a container process from an image is docker run
.
Let's understand how the command works by starting a ruby container.
If you run docker run ruby
for the first time, it will first fetch the ruby image in its latest version from the docker hub and download it into your local docker.
You'll get an output similar to this one:
Unable to find image 'ruby:latest' locally
latest: Pulling from library/ruby
4c25b3090c26: Pull complete
1acf565088aa: Pull complete
b95c0dd0dc0d: Pull complete
5cf06daf6561: Pull complete
942374d5c114: Pull complete
420c5b579440: Pull complete
cdfe7730cd5c: Pull complete
bfeceb400e58: Pull complete
Digest: sha256:8849efdb1f006c5d7b26980f3aeb15f00fa2b5428cfbef8761aef5fc87491b89
Status: Downloaded newer image for ruby:3.0.2
Switch to inspect mode.
Ok, it looks like nothing happened yet but it does say that it downloaded the ruby image successfully. How can I confirm that?
The docker images
command
If you run docker images
you will see that you'll have the ruby with the latest
tag available for you. You should see something like this:
REPOSITORY TAG IMAGE ID CREATED SIZE
ruby latest b28f54b6ce55 3 days ago 881MB
Note that the created timestamp you see in the docker images list is the date that the image was added to the docker library and not the date of the download to your local docker.
Going back to our command, it should have also started a container based on this image but where is it?
The docker ps
command
Run docker ps
to list all the running containers.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
The list is empty. Why? The run command should have started a new container process.
If you run docker ps -a
where -a
is the flag to show all containers (running and exited), your container will be there.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
948b3179783a ruby:latest "irb" 10 seconds ago Exited (0) 5 seconds ago eager_snyder
Look at the Status column. It reads Exited (0) 5 seconds ago
. But why was this container exited?
You may not have noticed, amongst the printed download messages, but there was an error message at the end. Let's see that message again by re-running the docker run command:
-> docker run ruby
Switch to inspect mode.
This is because the ruby image by default sends irb
as a command to the container. Once the container process is opened the irb
command should run. But the container cannot run irb
unless we tell it to run in the interactive terminal mode.
In summary: the process was opened, the container ran irb
, got the error message, the process was closed right after it, exiting the container with it.
We can run any command with the interactive terminal mode using the -it
flag.
-> docker run -it ruby
irb(main):001:0>
Yeah, our sandbox is running! ๐
Go ahead and play with ruby a bit.
irb(main):001:0> ['hello', 'world'].join(', ').capitalize
=> "Hello, world"
Before you exit irb
, open another terminal window and run docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2ef882ad2b5b ruby:latest "irb" 4 minutes ago Up 4 minutes elated_satoshi
There it is! We have a running container!
Now exit irb
and run docker ps
again. It's empty. If you run docker ps -a
you should see your container there. It has an exited status again.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2ef882ad2b5b ruby:latest "irb" 6 minutes ago Exited (0) 47 seconds ago elated_satoshi
948b3179783a ruby:latest "irb" 15 minutes ago Exited (0) 15 minutes ago eager_snyder
We've just learned another essential aspect of containers. The container has one main process. If that process stops, the container stops. When we left irb
, the process stopped and the container was exited.
Note also that we have two stopped containers. The first container with the random name elated_satoshi
is the one we've just opened and closed after playing with irb
, and the last one eager_snyder
is the container that was created when we ran the command without the interactive terminal flag.
So, every time you run the ruby image, you're creating a different container.
Passing other commands to the container
We've seen that running docker run -it ruby
is the same as running docker -it ruby irb
. We omit it because that's the default for ruby, but we can pass other commands for the container to run once it's opened.
For instance, if we run docker run -it ruby bash
we will create an interactive bash shell in the container.
docker run -it ruby bash
root@b1c73a63eef9:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@b1c73a63eef9:/# ruby -v
ruby 3.0.2p107 (2021-07-07 revision 0db68f0233) [x86_64-linux]
root@b1c73a63eef9:/#
Stopping and removing containers
As we saw earlier, every time you run the docker run
command a new container will be created. This means that with time you'll get a big list of stopped containers:
If you'd like to remove stopped containers, you can just run docker rm <container id>
.
If the container is still running you might have to use the stop
or kill
command before removing it.
docker stop <container id>
docker rm <container id>
If you'd like to avoid exited containers to be kept after the process is closed there's a useful flag you can use with docker run
, the -rm
.
docker run -rm -ti ruby
Keeping container processes open
Remember that every time we left irb
or bash
in the previous exercises, we closed the process and the container.
But there's a way you can exit yourself from the process without closing it. If you use ctrl+p
+ ctrl+q
, you will no longer see the process but it will still be running in the background. If you run docker ps -l
, where the -l
flag will get the latest container, you'll see your container there.
If you want to start a new container in the detached mode you can pass the detach flag, -d
:
-> docker run -d -ti ruby
e68f13762ee83ea315b3d0a679432dd1963f276deb387aa3bdce9b4801f21979
It prints out the container id
for you, but again, you can also confirm that there's a new process running with docker ps
.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e68f13762ee8 ruby:latest "irb" 4 seconds ago Up 3 seconds crazy_varahamihira
If you want to jump in the process again, you can run the docker attach <container id>
command.
-> docker attach e68f13762ee8
irb(main):001:0>
You can also add a new process to the same container, with the exec
command. Imagine you have irb
running in the background but you want to jump in the bash:
docker exec -it e68f13762ee8 bash
root@e68f13762ee8:/#
Naming containers
As we noticed earlier, a container has a name but if you don't give it one, a random one will be assigned to it.
If you want to give it a name, you can pass it in a --name
flag.
docker run --name ruby-sandbox -ti ruby`
And now run docker ps
to make sure there's a container with that name:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
aee3cd98a190 ruby:latest "irb" 10 seconds ago Up 9 seconds ruby-sandbox
First master the fundamentals!
A lot of commands were covered in this article but I believe that there is only a handful of fundamental concepts in docker. The goal of this article was to share a bit of the process and exercises I've gone through to understand what docker really is. It's a compilation of stuff I was first taught by my colleague Leandro (he has great content on this topic, go check him out) and that I also got from basic docker courses. Have fun! ๐ณ
Top comments (0)