Our First Containers
The wait is finally over. It’s time to roll up our sleeves and start our first container. Get Ready.
Tasks:
Task 1: Running your first container
Now that Docker is set up, it’s time to get our hands dirty. In this section, you will run an Alpine Linux container (a lightweight Linux distribution) on your system and get hands-on with the docker container run command.
-
To get started, let’s run the following in our terminal:
$ docker image pull alpine Unable to find image 'alpine:latest' locally latest: Pulling from library/alpine 88286f41530e: Pull complete Digest: sha256:f006ecbb824d87947d0b51ab8488634bf69fe4094959d935c0c103f4820a417d Status: Downloaded newer image for alpine:latest
Note: Depending on how you’ve installed docker on your system, you might see a
permission deniederror after running the above command. If you’re on Linux, you may need to prefix yourdockercommands withsudo. Alternatively you can create a docker group to get rid of this issue.
-
The
pullcommand fetches the alpine image from the Docker registry and saves it in your system. You can use thedocker image lscommand to see a list of all images on your system.$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE alpine latest 3fd9065eaf02 2 weeks ago 7.8MB hello-world latest f2a91732366c 2 months ago 13.3kB
Run a single-task Alpine Linux Container
-
Great! Let’s now run a Docker container based on this image. To do that, you use the
docker container runcommand.$ docker container run alpine hostname 888e89a3b36
What happened? Behind the scenes, a lot of stuff happened. When you call docker container run,
- The Docker client contacts the Docker daemon to check if the alpine image is available locally; if not, it downloads it from Docker Hub. (Since we have issued
docker pull alpinebefore, the download step is not necessary) - The Docker daemon creates the container and then runs a command in that container
- The Docker daemon streams the output of the command to the Docker client
When you run docker container run alpine hostname, you provided the command (hostname). Docker then started the container, executed the specified command in this container, and returned its hostname (888e89a3b36).
-
Docker keeps a container running as long as the process started inside the container is still running. In this case, the hostname process completes when the output is written, so the container exits. The Docker platform doesn’t delete resources by default, so the container still exists in the Exited state.
List all containers:
$ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 888e89a3b36b alpine "hostname" 50 seconds ago Exited (0) 49 seconds ago awesome_elionNotice that your Alpine Linux container is in the
Exitedstate.Note: The container ID is the hostname that the container displayed. In the example above, it’s 888e89a3b36b
Containers that do one task and then exit can be handy. You could build a Docker image that executes a script to configure something. Anyone can perform that task just by running the container - they don’t need the actual scripts or configuration information.
-
Let’s try something more exciting.
$ docker container run alpine echo "hello from alpine" hello from alpineOK, that’s some actual output. In this case, the Docker client ran the
echocommand inside our alpine container and exited it. If you’ve noticed, all of that happened pretty quickly. Compare the same process to booting up a virtual machine, running a command, and then killing it. Now you know why they say containers are fast! -
Try another command:
$ docker container run alpine /bin/shWait, nothing happened! Is that a bug? Well, no. These interactive shells will exit after running any scripted commands unless they run in an interactive terminal - so for this example to not exit, you need to run:
$ docker container run -it alpine /bin/sh
You are now inside the container shell, and you can try out a few commands like ls -l, uname -a, and others. Exit out of the container by giving the exit command.
-
Now it’s time to see the
docker container lsor the shortcutdocker pscommand. Thedocker container lscommand shows you all containers that are currently running.$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -
Since no containers are running, you see a blank line. Let’s try a more helpful variant:
docker container ls -a$ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 36171a5da744 alpine "/bin/sh" 5 minutes ago Exited (0) 2 minutes ago fervent_newton a6a9d46d0b2f alpine "echo 'hello from alp" 6 minutes ago Exited (0) 6 minutes ago lonely_kilby 888e89a3b36b alpine "hostname" 8 minutes ago Exited (0) 8 minutes ago elated_ramanujan c317d0a9e3d2 hello-world "/hello" 34 seconds ago Exited (0) 12 minutes ago stupefied_mcclintock -
You see above a list of all the containers that you ran. Notice that the
STATUScolumn shows these containers exited a few minutes ago. You’re probably wondering if there is a way to run more than just one command in a container. Let’s try that now:$ docker container run -it alpine /bin/sh / # ls bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var / # uname -a Linux 97916e8cb5dc 4.4.27-moby #1 SMP Wed Oct 26 14:01:48 UTC 2016 x86_64 LinuxType
exitorCTRL-Dto exit the interactive container. Once we exit the container, it will also exit and stop.Running the
runcommand with the-itflags attaches us to an interactive tty in the container. Now, you can run as many commands in the container as you want. Take some time to run your favorite commands.
That concludes a whirlwind tour of the docker container run command, which you’ll often use. It makes sense to spend some time getting comfortable with it. To find out more about run, use docker container run --help to see a list of all flags it supports. As you proceed, we’ll see a few more variants of docker container run.
Task 2: Run an interactive Ubuntu container
You can run a container based on a different version of Linux than what is running on your Docker host.
In the following example, we will run an Ubuntu Linux container.
-
Run a Docker container and access its shell.
In this case we’re giving the
docker container runcommand three parameters:--interactivesays you want an interactive session--ttyallocates a pseudo-tty--rmtells Docker to go ahead and remove the container when it’s done executing
The first two parameters allow you to interact with the Docker container.
We’re also telling the container to run
bashas its main process (PID 1).$ docker container run --interactive --tty --rm ubuntu bashWhen the container starts, you’ll drop into the bash shell with the default prompt
root@<container id>:/#. Docker has attached to the shell in the container, relaying input and output between your local session and the shell session in the container. -
Run some commands in the container:
ls /- lists the contents of the root directoryps aux- shows all running processes in the container.cat /etc/issue- shows which Linux distro the container is running (e.g.,Ubuntu 24.04 LTS)
-
Type
exitto leave the shell session. This will terminate thebashprocess, causing your container to exit.Note: As we used the
--rmflag when we started the container, Docker removed that container when it stopped. This means if you run anotherdocker container ls --allyou won’t see the Ubuntu container. -
For fun, let’s check the version of our host OS
$ cat /etc/issue Ubuntu 24.04 LTS \n \lNote: On macOS or Windows, this file won’t exist on your host since Docker Desktop runs containers inside a Linux VM. The key takeaway is that the distribution of Linux inside the container does not need to match the host OS.
Interactive containers are helpful when you are putting together your image. You can run a container, verify all the steps you need to deploy your app and capture them in a Dockerfile.
Note: You can commit a container to make an image from it - but you should avoid that wherever possible. It’s much better to use a repeatable Dockerfile to build your image. You’ll see that shortly.
-
To exit the shell of the Ubuntu container:
$ exit
We can exit the TTY of the container by typing exit or CTRL-D
Task 3: Run a background MariaDB container
Background containers are how you’ll run most applications. Here’s a simple example using MariaDB.
-
Let’s run MariaDB in the background using the
--detachflag. We’ll also use the--nameflag to name the running containermydb.We’ll also use an environment variable (
--env) to set the root password (NOTE: DON’T DO THIS IN PRODUCTION):$ docker container run \ --detach \ --name mydb \ --env MARIADB_ROOT_PASSWORD=my-secret-pw \ mariadb:latest Unable to find image 'mariadb:latest' locally latest: Pulling from library/mariadb 6c7698a779f6: Pull complete c3beef926275: Pull complete <Snip> fbc99aa6f426: Pull complete Digest: sha256:dd51b32c5c5c6ed56019bb92f48b4f749287208b1b903ac61ef1efa6c2ae2410 Status: Downloaded newer image for mariadb:latest 762950d93224b25b465167a5b9862cd208d5d4577715aa4dc05b36f898a8b9b0Once again, the requested image was unavailable locally, so Docker pulled it from Docker Hub.
As long as the MariaDB process runs, Docker will keep the container running in the background.
-
List running containers
$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3f4e8da0caf7 mariadb:latest "docker-entrypoint..." 52 seconds ago Up 51 seconds 3306/tcp mydbNotice your container is running
-
You can check what’s happening in your containers by using a couple of built-in Docker commands:
docker container logsanddocker container top$ docker container logs mydb <output truncated> 2023-06-15 8:29:26 0 [Note] Server socket created on IP: '0.0.0.0'. 2023-06-15 8:29:26 0 [Note] Server socket created on IP: '::'. 2023-06-15 8:29:26 0 [Note] mariadbd: ready for connections. Version: '11.0.2-MariaDB-1:11.0.2+maria~ubu2204' socket: '/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distributionThis shows the logs from your Docker container.
Let’s look at the running processes inside the container.
$ docker container top mydb UID PID PPID C STIME TTY TIME CMD 999 23256 23229 0 08:29 ? 00:00:00 mariadbdYou should see the MariaDB demon (
mariadbd) is running. Note that the PID shown here is the PID for this process on your docker host. To see the samemariadbdprocess running as the main process of the container (PID 1) try:$ docker container exec mydb ps -ef UID PID PPID C STIME TTY TIME CMD mysql 1 0 0 08:29 ? 00:00:00 mariadbd root 139 0 0 08:30 ? 00:00:00 ps -efNote: If the
pscommand is not installed, you can install it with the following command:apt update && apt install -y procpsAlthough MariaDB runs, it is isolated within the container because no network ports have been published to the host. Network traffic cannot reach containers from the host unless ports are explicitly published.
-
List the MariaDB version using
docker container exec.docker container execallows you to run a command inside a container. In this example, we’ll usedocker container execto run the command-line equivalent ofmariadb --user=root --password=my-secret-pw --versioninside our MariaDB container.$ docker container exec -it mydb \ mariadb --user=root --password=my-secret-pw --version mariadb from 11.0.2-MariaDB, client 15.2 for debian-linux-gnu (aarch64) using EditLine wrapperThe output above shows the MariaDB version number, as well as a handy warning.
Note: We use the actual password value here because
$MARIADB_ROOT_PASSWORDis an environment variable inside the container, not on the host. If you prefer, you can usedocker container execto start a shell inside the container first and then reference the variable there (as shown in step 6). -
You can also use
docker container execto connect to a new shell process inside an already-running container. The command below will give you an interactive shell (sh) in your MariaDB container.$ docker container exec -it mydb sh #Notice that your shell prompt has changed. This is because your shell is now connected to the
shprocess running inside of your container. -
Let’s check the version number by running the same command we passed to the container in the previous step.
# mariadb --user=root --password=$MARIADB_ROOT_PASSWORD --version mariadb from 11.0.2-MariaDB, client 15.2 for debian-linux-gnu (aarch64) using EditLine wrapperNotice the output is the same as before.
-
Type
exitto leave the interactive shell session.Your container will still be running. This is because the
docker container execcommand started a newshprocess. When you typedexit, you exited theshprocess and left themariadbdprocess still running.
Let’s clean up for the next lab.
-
Stop the MariaDB container
$ docker container stop mydb -
Remove the MariaDB container
$ docker container rm mydb -
Delete the MariaDB image
$ docker image rm mariadb
Terminology
In the last section, you saw a lot of Docker-specific jargon that might confuse some. So, before you go further, let’s clarify some terminology used frequently in the Docker ecosystem.
- Images - The file system and configuration of our application, which are used to create containers. To learn more about a Docker image, run
docker image inspect alpine. You used thedocker image pullcommand to download the alpine image. When you executed the commanddocker container run hello-world, it also did adocker image pullbehind the scenes to download the hello-world image. - Containers - Running instances of Docker images — containers run the actual applications. A container includes an application and all of its dependencies. It shares the kernel with other containers and runs as an isolated process in user space on the host OS. You created a container using
docker container run, which you did using the alpine image you downloaded. You can see a list of running containers by using thedocker container lscommand. - Docker daemon - The background service running on the host, that manages building, running and distributing Docker containers.
- Docker client - The command line tool that allows the user to interact with the Docker daemon.
- Docker Hub - A registry of Docker images where you can find trusted and enterprise-ready containers, plugins, and Docker editions. You’ll be using this later in this tutorial.
Next Steps
For the next step in the tutorial, head over to Webapps with Docker - Part One