Mimi Magusin

July 4, 2018

Three Days of Docker (2)

In February 2017 we taught an advanced session on Docker basics as part of our Acceleration Program. Not much later, we restructured out advanced classes and this class was just hanging around, almost forgotten…

That’s why we decided to publish it here, for all to enjoy 👏.

🎯 Things you will learn about in this series:
✔️ Servers & Clusters of Servers
✔️ Services & other dependencies
✔️ Processes & Environments
✔️ Configuring applications
✔️ Building an app for production
✔️ Docker
✔️ Docker Compose

Yesterday, you had a look at web-servers in general, why you would want to use Docker in the first place and how to set it up on your machine. Tomorrow, you will interact with deamon and set up your docker file. But first…. let’s see what Docker images and containers are all about and how we can set up PostgreSQL in our docker environment!

Using Docker

The Docker Hub

The Docker Hub is like Github for Docker images. A Docker image consists of multiple layers, that together form a complete filesystem that contain all the instructions and data to run a Docker container.

An image is first built from a set of commands. Each command changes the container, and Docker creates image layers for each of these commands. Similar to git commits!

A command that produces such a slice (layer) can be installing packages with apt-get, or adding some data from a folder on your file system.

After an image is pushed, it can be pushed to a Docker registry like Docker Hub. Images that are pushed can in turn be pulled from a registry so we can run it without having to build it first.

This can be very useful, because it can save you a lot of time! Let’s say you want to run Ubuntu version 16.04 to try something out. We can pull an Ubuntu 16.04 image:

docker pull ubuntu:16.04

(The example below is run with ubuntu 14.04, the principle is the same)

📌 Do you see how it pulls each layer of the image?

We will have a look at how these images are created in the next section.

A Docker Container

A Docker container is created from the Docker image. It is called a container because it is a contained environment in which you run processes. Since they are contained, different containers cannot (by default) talk to each other.

That sounds a bit weird. Let me explain.

Let’s say I want to run a command on Ubuntu 16:04, but I don’t have it installed on my computer. Well, tough luck! Unless we have Docker! Now, I can start a container and run a command INSIDE a contained Ubuntu 16.04 environment.

We will run the uname command to print out some info about the system:

docker run --rm ubuntu:16.04 uname -a
# Linux 68f7c88732cb 4.4.15-moby #1 SMP Thu Jul 28 22:03:07 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

Compare the above to running the command directly on your laptop. On an OSX laptop it could give:

uname -a
# Darwin reinbook.local 15.6.0 Darwin Kernel Version 15.6.0: Thu Jun 23 18:25:34 PDT 2016; root:xnu-3248.60.10~1/RELEASE_X86_64 x86_64

Totally different! That’s because the first command was run inside the Ubuntu 16.04 environment, and the second inside the environment of a Mac OSX laptop.

Another experiment to show you I’m not making this up, let’s run the lsb_release -a command:

docker run --rm ubuntu:16.04 lsb_release -a
# No LSB modules are available.
# Distributor ID: Ubuntu
# Description: Ubuntu 16.04.5 LTS
# Release: 16.04
# Codename: trusty

There you go! Ubuntu 16.04!

If I run this command on my Mac, the command does not even exist!

lsb_release -a
# zsh: command not found: lsb_release

Not convinced? Let’s try something even more useful then :)

Run a PostgreSQL server with Docker

Let’s start the latest PostgreSQL version inside a Docker container. Looking at the documentation for the postgres image on Docker Hub, we see that we need to run the following command:

docker run --name my-postgres -e POSTGRES_PASSWORD=mysecretpassword postgres
# Unable to find image 'postgres:latest' locally
# latest: Pulling from library/postgres
# 357ea8c3d80b: Pull complete
# c2c7a60f64c5: Pull complete
# 41ae9dccaf61: Pull complete
# f97dc66893de: Pull complete
# ff0ae6b27f85: Pull complete
# 0ad5d181a0a1: Pull complete
# da7e988cfd3f: Pull complete
# fc2b381c2104: Extracting 11.5 MB/41.84 MB
# ab13cd075635: Download complete
# d7ce612e91b2: Download complete
# 34dffd90d86f: Download complete
# ...

First time I run it, we have to pull the image first, as you can see above.

After it’s pulled, it will start a PostgreSQL server:

# ...
#
# PostgreSQL init process complete; ready for start up.
#
# LOG: database system was shut down at 2016-08-23 19:33:36 UTC
# LOG: MultiXact member wraparound protections are now enabled
# LOG: database system is ready to accept connections
# LOG: autovacuum launcher started

There you go! You’re running the latest PostgreSQL server! Let’s try and connect to it.

🎓 Run docker run --name my-postgres -e POSTGRES_PASSWORD=mysecretpassword postgres in your terminal.
🎓 Start a new terminal (leave this docker container running). Then use psql to connect to the container.
docker run -it --rm --link my-postgres:postgres postgres psql -h postgres -U postgres
# Password for user postgres:
# psql (9.5.4)
# Type "help" for help.
#
# postgres=#
Note that it asks for that password that we put in in the previous command, “mysecretpassword”.

But cool! Now we are in a psql session in a Docker container! But wait, there is more. A lot more, actually, happening here.

Let’s start by breaking down the first command:

docker run --name my-postgres -e POSTGRES_PASSWORD=mysecretpassword postgres

This part makes sense:

docker run
  • then:
--name my-postgres

This gives your running container a name! We need that later, so we can connect to it. You can run multiple containers from the same image with different names too.

🌟 If this sounds strange, you could compare it to object oriented programming. Images compare to classes, while containers compare to instances of that class.

Let’s start another one (in a new terminal!) with the name elephant:

docker run --name elephant -e POSTGRES_PASSWORD=mysecretpassword postgres

Ok. Now have a look at all the running Docker containers:

docker ps
# CONTAINER ID IMAGE ... NAMES
# 76cc4b532957 postgres ... elephant
# f2c140513028 postgres ... nostalgic_mcnulty
# de8faab0b7f5 postgres ... my-postgres

Back to the command we were dissecting:

  • Then we are setting an environment variable, with the -e switch:
-e POSTGRES_PASSWORD=mysecretpassword

This is used in the image’s instructions to set up authentication. It is the password you have to provide to connect to the database.

And finally, the last part:

postgres

That’s just the name of the image. And without a version (like ubuntu:16.04) it will pull the image that is tagged latest. Each time a new version of an image is pushed to docker hub, it automatically gets the latest label assigned.

Lets look at the client command:

docker run -it --rm --link my-postgres:postgres postgres psql -h postgres -U postgres

First part is known by now:

docker run

Then this switch:

-it

This stands for interactive and tty (connect a terminal). We use these together if we want to run a command, and connect to it via a terminal session, so we can interact with it. In the case of psql that makes sense, so we can type in commands there.

Next up:

--rm

Every time we run a container, Docker stores the state of that container after you stop it. We don’t want that if we are just using the psql command, so we tell the docker client that it is OK to remove it after we're done.

Next part is amazing. Check this out:

--link my-postgres:postgres

Earlier we stated that containers are, well…, contained. They cannot communicate to each other. If you want to have inner-container communication, this is the way to set it up.

Here, we link the other container (my-postgres) that runs the server, to this container, and we will name it postgres. Name it, meaning: giving it a hostname so we can connect to it.

Then the obvious one to tell Docker which image to use:

postgres

And finally the command we want to run in that container:

psql -h postgres -U postgres

This psql command connects to the host postgres (remember the --link?) with the default Postgres user, postgres.

Run a query

🎓 Run an SQL query from the psql prompt we have there:
SELECT * FROM elephants;

This command will fail, because the relation (table) “elephants” does not exist.

🎓 Have a look at the other postgres container that runs the server. How can you know that you connected to this server from that other container?

Create a table

🎓 Create a table called elephants, with one column for a name for each elephant:
CREATE TABLE "elephants" ("id" serial primary key, "name" character varying);
🎓 Now, start a new docker container with a new psql session, and insert some data:
INSERT INTO "elephants" ("name") VALUES ('Dumbo');
INSERT INTO "elephants" ("name") VALUES ('Echo');
INSERT INTO "elephants" ("name") VALUES ('Castor');
INSERT INTO "elephants" ("name") VALUES ('Pollux');
🎓 Now exit the psql container.
🎓 Start a new psql container, and check if the data is available that you just created from the previous container:
SELECT * FROM "elephants";
🎓 Now ^C out of the server container. See that the running psql containers loose their connections with the server:
SELECT * FROM "elephants";
-- FATAL: terminating connection due to administrator command
-- server closed the connection unexpectedly
-- This probably means the server terminated abnormally
-- before or while processing the request.
-- The connection to the server was lost. Attempting reset:
-- Failed.
🎓 To run the postgres container we could try and run the same command again:
docker run --name my-postgres -e POSTGRES_PASSWORD=mysecretpassword postgres

Note that the above command gives an error now!

# docker: Error response from daemon: Conflict. The name "/my-postgres" is already in use by container de8faab0b7f5253ccc19cf1b908d7e3af3d262e7e89d6e5c9df072d400d5f835. You have to remove (or rename) that container to be able to reuse that name..
# See 'docker run --help'.

The container with that name already exists! That means we ran it without the --rm flag, so Docker keeps the container around, for later use.

You can start the postgres server again with the docker start command, and the name we gave it earlier:

docker start -a my-postgres
🎓 Now start the server and a new client. Is the data still there?

NoSQL

🎓 Start a Redis server from the redis image, give it a name and connect to it from another container with the redis-cli command. You can pass in a hostname to redis-cli with the -h flag.
🎓 Now play a bit with Redis, following the commands in this tutorial here.

Conclusion

Did you manage, all the way to the end? Did you enjoy being in an ubuntu environment for just a moment? Did you manage to run your queries? Do you still have some questions? Let us know in the comments, and see you soon at the final part of this series: Three days of Docker!


Three Days of Docker (2) was originally published in Codaisseur Academy on Medium, where people are continuing the conversation by highlighting and responding to this story.

Share this page