Hello Docker!
To get us started let's run a simple Docker container. This is the Hello World
of Docker.
In your codespace environment, you can essentially maximize the terminal by dragging it to the top of the screen. This will give you more space to work with.
In the terminal, run the following command:
docker run hello-world
You should then see something like the following:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:d211f485f2dd1dee407a80973c8f129f00d54604d2c90732e8e320e5038a0348
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
```
Let's have a look at each part in more detail:
```text
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c1ec31eb5944: Pull complete
Digest: sha256:d211f485f2dd1dee407a80973c8f129f00d54604d2c90732e8e320e5038a0348
Status: Downloaded newer image for hello-world:latest
This part of the output shows that Docker was unable to find the hello-world
image locally, so it pulled it from the Docker Hub. The hello-world
image is a very small image that is used to test that your Docker installation is working correctly.
latest:
is a tag for the image. Tags are used to identify different versions of an image. In this case, the latest
tag is used to identify the latest version of the hello-world
image.
c1ec31eb5944: Pull complete
is the layer ID of the image that was pulled. Docker images are made up of multiple layers, and each layer is identified by a unique ID. Since the hello-world
image is very small, it only has one layer.
Digest: sha256:d211f485...
is a unique hash of the image. This hash is used to uniquely identify the image and its contents.
If you try to run the docker run hello-world
command again, you should just see the Hello from Docker!
message without the other output. This is because Docker has already pulled the hello-world
image and cached it locally.
The actual content of the container can be seen in the message displayed by the container.
It also gives us the following hint:
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Ubuntu in a Container
So, let's try that next:
docker run -it ubuntu bash
You should now see a new prompt that looks something like this:
root@f4b5c7e4b6b4:/#
This prompt indicates that you are now inside a Docker container running a Ubuntu image. The root@f4b5c7e4b6b4
part is the hostname of the container (yours may be different), and the /#
part is the command prompt.
Here is an overview of the commands we used:
docker run # Base command to create and start a new container
-i # Interactive - keep STDIN open (allows you to type into container)
-t # Allocate a pseudo-Terminal (gives you the shell prompt)
ubuntu # The image to use (in this case, official Ubuntu image)
bash # The command to run inside container (start a bash shell)
We can combine tags to make the command shorter: -it
is the same as -i -t
. Without -it
:
-i
only: You can send input but display will be weird-t
only: You get nice formatting but can't type input- neither: Container runs the command and exits unless it has a foreground process
A "bash shell" is the command line interface (CLI). It so happens that if we run:
docker run -it ubuntu
docker run -it ubuntu sh
to get a simple shell instead of bash. You can also do
docker run -it ubuntu zsh
When we are inside the container, if we run:
cat /etc/os-release
You should see the following output:
PRETTY_NAME="Ubuntu 24.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.1 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
confirming that you are indeed running a Ubuntu container. This container does not have any additional software installed, so you have a clean Ubuntu environment to work with. To exit the container, you can type exit
and press Enter
.
Python in a Container
Let's grab a python container:
docker run -it python bash
We are now inside a docker container with python, and we can run python commands:
python --version
Python 3.13.0
We can also run python and play around with it:
python
>>> print("Hello, Docker!")
Hello, Docker!
>>> exit()
You can directly access the Python REPL while running the container:
docker run -it python python
If you exit the REPL in the usual way, then you will also exit the container.
Seeing the Containers and Images
In order to see a list of images that we have pulled, we can run:
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
python latest c41ea8273365 4 weeks ago 1.02GB
python 3.12-slim-bookworm 668757ec60ef 4 weeks ago 124MB
ubuntu latest fec8bfd95b54 5 weeks ago 78.1MB
hello-world latest d2c94e258dcb 18 months ago 13.3kB
To see a list of all containers, we run:
docker ps -a
You should see something like this:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5cb8b1bbe52f ubuntu "bash" 13 minutes ago Exited (127) 13 minutes ago wizardly_cartwright
80040792ac55 python:3.12-slim-bookworm "bash" 14 minutes ago Exited (0) 13 minutes ago heuristic_kilby
42129b1076cd python "python" 28 minutes ago Exited (0) 20 minutes ago peaceful_raman
112c227987a6 python "zsh" 28 minutes ago Created confident_lalande
724386298bc2 python "sh" 28 minutes ago Exited (0) 28 minutes ago nervous_brahmagupta
4e490eb53aaf python "bash" 34 minutes ago Exited (0) 28 minutes ago quirky_bohr
21eea7f7d3b4 ubuntu "bash" 35 minutes ago Exited (0) 34 minutes ago admiring_payne
19379f07e484 hello-world "/hello" 35 minutes ago Exited (0) 35 minutes ago adoring_keller
note the colourful names...
We can remove containers with:
docker rm <container_id>
docker container prune
Remove all images with:
docker rmi -f $(docker images -aq)
Now if you run docker ps -a
you should see an empty list.
Naming containers
You might have noticed that the containers have random names, and that if you want to stop them, you have to use the ID, which is cumbersome. So instead, you can name the container when you run it:
docker run -it --name mycontainer -it python bash
Now I can remove the container with:
docker rm mycontainer
Preserving information
Let's run the python container again
docker run -it python bash
and try to create a directory:
cd home
mkdir mydir
If you run ls
you should see the mydir
directory. Now see what happens when you close down the container and start it again:
docker run -it python bash
cd home
ls
There is nothing there! What is going on? First we need to see exactly why this is happening. Run the docker ps -a
command, and you should see the following:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0b89d0ef62fa python "bash" 17 seconds ago Exited (0) 4 seconds ago vigorous_gould
90ff3f1f1085 python "bash" 34 seconds ago Exited (0) 25 seconds ago distracted_bohr
So we have actually created two containers, and we are not using the same one. This is because when we run the docker run
command, we are creating a new container each time. This is why the changes we made in the first container are not present in the second container.
So instead I can restart my previous container and attach to it:
docker start -ai distracted_bohr
Now when I change into the home
directory and run ls
, I should see the mydir
directory. The above command is saying "start the container distracted_bohr
and attach to it interactively".
Generally speaking, we do not want to store data in a container. This includes creating files, directories, and databases. If we store files inside the container, they will be lost when the container is stopped or removed:
-
Persistence Beyond the Lifecycle of the Container Containers are ephemeral by design. If you need to update or recreate a container (e.g., pulling a new image), the data stored directly in the container is lost unless explicitly backed up. Mounted volumes persist independently of the container lifecycle, ensuring your data is safe even if the container is removed.
-
Ease of Data Sharing Mounted volumes allow data to be shared between multiple containers. For example, if you have one container for a database and another for a web application, both can share a mounted volume for logs or configurations.
-
Integration with Host Filesystem With a mounted volume, you can directly edit files from your host system (e.g., code tracked in Git), and changes will be reflected in the container in real-time. This makes development workflows more efficient and eliminates the need for repeated docker cp commands.
-
Simplified Version Control with Git If you're using Git, you likely want your working directory (e.g., /app in the container) to correspond to your Git repository on the host. Mounting the directory ensures that any changes made in the container are tracked by Git on the host, simplifying version control and collaboration.
-
Backup and Portability Mounted volumes are easier to back up and migrate. You can copy a directory from your host system for safekeeping or move it to another machine. Data stored inside a container is harder to extract and manage outside of Docker.
-
Security and Isolation Using mounted volumes can help isolate the container's internal state from persistent data. This separation reduces the risk of accidental data loss due to container mismanagement.
This is why containers are often used for running stateless applications that do not need to store data between runs. So how can we store data in a container? We can use volumes.