Since Docker is “something” that let’s you use “something else”,
let’s make that something do something with something else.
Essentially, Docker is an engine that allows you to run container-images, in containers.

Getting inside…

Let’s try this:

sudo docker run --rm -it --name ubuntu_test ubuntu bash 

Flags Explained:

  • --rm: Removes the container once stopped.
  • -it: Enables interactive mode.
  • --name ubuntu_test: This will give a name to my container (otherwise a random name will be given)
  • ubuntu: The name of the image to pull and create a container from.
  • bash: The command to run as the container starts up.

You’ll now find yourself inside a Ubuntu OS container, in its / directory to be precise. This system is completely isolated from the host computer and once we exit it, it will be deleted. Next, let’s run a quick whoami command and exit the container by running the exit command.

root@f735cd301d07:/# whoami
root
root@f735cd301d07:/# exit

What just happenned??

When we ran the docker command, the following steps happened under the hood:

  • Docker daemon searched for the Ubuntu image locally and didn’t find it.
  • It then (the daemon) contacted the official Docker Hub and pulled (downloaded) the image.
  • Once the image was downloaded it created a container based on that image
  • Lastly it ran the bash command in -it mode which means ‘Interactive’. At this time, the process bash was running inside the container and you could give it input and recieve its output - you were doing things “inside the container”. Once you exited the container, the container stopped because since there’s no process running, there’s no need for it to run. And once it stopped it also got deleted becase of the --rm flag, and you’ve found yourself back on your host machine.
    Though this is not typically how you’d use a container though, this method has it use cases.

Volumes and Mounts/Binds

So so if the container is deleted when stopped or updated, what happens if we need persistent data? There are two common methods to do this:

  • Persistent Volume: This is a Docker object that you can then run the inspect command on, and will store any persistent data. You attach it to a container when you start/run it and when the container updates, you reattach it to the new container.
  • Folder Binds/Mounts (They’re the same thing): In this method, you simply map a current folder on the host computer to a location inside the container. Whatever files are available there, will also be available from inside the container. This folder can also be writted to/deleted from.

Folder Mounts/Binds (Hands-On)

Let’s create a folder named my_share in the current folder and then, create a file inside that folder my_file.txt:

mkdir -p ./my_share
touch my_share/my_file.txt

Then run this command to create a Ubuntu container mounting the new folder inside:

sudo docker run --rm -it -v ./my_share:/var/my_share --name ubuntu_test ubuntu 

Next, lets list the content of /var/my_share/ by running:

ls -l /var/my_share

You can see my_file.txt, the file we created in the host computer now is presesnt inside the container.

Now let’s create a file from withing the container in the same folder and exit the container:

touch /var/my_share/my_new_file.txt
exit

Now, if we list the content of ./my_share/ from inside the host, we should see the two files. The first, we created while inside the host and was available from inside the container, and the second, we created inside the container and was visible from the host. I hope this is starting to make sense.

Flags Explained:

  • --rm: Removes the container once stopped.
  • -it-: Enables interactive mode.
  • -v ubuntu_data:/var/ubuntu_data: The -v is for ‘volume’ whatever’s to the LEFT of the : is the path of the folder on the HOST computer, and to the RIGHT of the : is the path inside the container where the ‘host folder’ is to be mounted. We’ve mapped ./my_share/ on the host’s side to /var/my_share inside the container.
  • --name ubuntu_test: This will give a name to my container (otherwise a random name will be given)
  • ubuntu : The name of the image to pull and create a container from.

Persistent Volume (Hands-On)

Now let’s create a new Ubuntu container with a persistent volume.

sudo docker run --rm -it -v ubuntu_data:/var/my_share --name ubuntu_test ubuntu 

Notice how on the left of the : is not a path, it’s a name of a persistent volume that will be created. After running the above command, we’ll be inside the new container and can create a new file and exit the container:

touch /var/my_share/my_new_file.txt
exit

Now lets run the inspect command:

sudo docker volume inspect ubuntu_data

The output should look something like this:

[
    {
        "CreatedAt": "2024-06-15T16:52:51Z",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/ubuntu_data/_data",
        "Name": "ubuntu_data",
        "Options": null,
        "Scope": "local"
    }
]

Now let’s list the folder mentioned in the "Mountpoint" key, we’ll need to do this as sudo.

sudo ls /var/lib/docker/volumes/ubuntu_data/_data

There you should see the file we created while inside the container.

Summary:

There are two main methods to handle persistent storage with containers. Persistent Volumes and Folder Bindings. They both have their advantages and limitations. However each project will have its own implementation based on its requirements.

  • Note: You can also use network shares but that’s outside the scope of this tutorial.

Docker Journey

So you know all about Docker images, containers and volumes. You’re ready to create your first REAL usable service! Follow this Link to learn about docker-compose, and how it can help you deploy an infrastructure containing multiple containers hosting multiple services.