Running a full-blown application under Docker

Objectives

Run a sample standalone app under docker.

Login as root

ssh into your group’s host server, and switch to the “root” user:

$ sudo -s
#

Networked application example: Keycloak

Keycloak is a fairly complex application to build and install (it depends on Java). Here we’ll show how easy it is to get up and running in a docker container.

Because it’s a networked application, we’ll need to enabling forwarding of traffic on a TCP port to reach it.

Run the following command:

# docker run -p 8443:8443 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak quay.io/keycloak/keycloak:15.0.2

You should find the application startup logs written to the console. While that happens, here’s an explanation of the flags:

After a few seconds it will finish initializing:

...
19:12:32,983 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 15.0.2 (WildFly Core 15.0.1.Final) started in 12048ms - Started 692 of 977 services (686 services are lazy, passive or on-demand)
19:12:32,984 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
19:12:32,984 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990

Now point your web browser at port 8443 on your own host, with HTTPS:

https://hostX.ws.nsrc.org:8443/

Accept the self-signed certificate. Click on “Administration Console”. Login as “admin” and “admin”. You have a running keycloak server!

Stopping and starting

Go back to where you see the console logs. Stop the container by hitting ctrl-C. This should return you back to the shell prompt.

...
19:27:33,880 INFO  [org.infinispan.CLUSTER] (ServerService Thread Pool -- 78) ISPN000080: Disconnecting JGroups channel ejb
19:27:33,891 INFO  [org.jboss.as] (MSC service thread 1-2) WFLYSRV0050: Keycloak 15.0.2 (WildFly Core 15.0.1.Final) stopped in 72ms
*** JBossAS process (201) received TERM signal ***
root@hostX:~#

Now try running the container again using the exact same command. What happens this time?

# docker run -p 8443:8443 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak quay.io/keycloak/keycloak:15.0.2
docker: Error response from daemon: Conflict. The container name "/keycloak" is already in use by container "7cf852e94fc60fd480bdb474f17e4235c90517fa151a8ebf7f1828c9712179e0". You have to remove (or rename) that container to be able to reuse that name.
See 'docker run --help'.

What you’re trying to do is to create a new container with name “keycloak”. But you already have a container called “keycloak” (even though it’s stopped). You can see it like this:

root@s1:~# docker ps -a
CONTAINER ID   IMAGE                              COMMAND                  CREATED         STATUS                     PORTS                                       NAMES
7cf852e94fc6   quay.io/keycloak/keycloak:15.0.2   "/opt/jboss/tools/do…"   8 minutes ago   Exited (0) 2 minutes ago                                               keycloak

So what can you do now? You can simply restart the existing container:

# docker start keycloak
keycloak

This time, keycloak has started in the background - it’s returned straight back to the shell, and you can’t see the console logs it generates.

Those logs are still visible using the “docker logs” command:

# docker logs keycloak
...
19:30:40,111 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 15.0.2 (WildFly Core 15.0.1.Final) started in 9812ms - Started 692 of 977 services (686 services are lazy, passive or on-demand)
19:30:40,112 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
19:30:40,112 INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990

That’s all good.

Now let’s try running a second instance of keycloak. To do this we need to make a few things different:

# docker run -d -p 9443:8443 -e KEYCLOAK_USER=nsrc -e KEYCLOAK_PASSWORD=nsrc --name keycloak2 quay.io/keycloak/keycloak:15.0.2
0705359be2b9e84c96fb97922c00c29172a12714eff2c5b4d80b927bd3aa5704
#

This time it just prints the container ID (yours will be different) and returns to the shell. You can see both instances running:

# docker ps
CONTAINER ID   IMAGE                              COMMAND                  CREATED          STATUS          PORTS                                                 NAMES
0705359be2b9   quay.io/keycloak/keycloak:15.0.2   "/opt/jboss/tools/do…"   47 seconds ago   Up 46 seconds   8080/tcp, 0.0.0.0:9443->8443/tcp, :::9443->8443/tcp   keycloak2
7cf852e94fc6   quay.io/keycloak/keycloak:15.0.2   "/opt/jboss/tools/do…"   21 minutes ago   Up 56 seconds   8080/tcp, 0.0.0.0:8443->8443/tcp, :::8443->8443/tcp   keycloak

Now point your web browser at port 9443 on your own host, with HTTPS:

https://hostX.ws.nsrc.org:9443/

Once again, accept the self-signed certificate. Click on “Administration Console”. This time you’ll have to login as “nsrc” and “nsrc”.

You should note that we have two containers, but they were both started from the same published container image (keycloak).

Looking inside a container

It’s often possible to get a shell inside a running container, and this can be useful for debugging. Try the following:

# docker exec -it keycloak bash
bash-4.4$ hostname
44c432836df5
bash-4.4$ ps auxwww
bash: ps: command not found

The good news: there is a bash shell available in the container, and we can run it. Note that the container has its own “hostname” (which is the container ID), separate from the system hostname.

The bad news: many containers have a very cut-down environment with few utilities. In this case, even the most basic “ps” utility is missing.

We can have a look at what is in the /bin directory:

bash-4.4$ ls /bin
'['       cp           domainname           .... etc

To look at process information, we may need to get creative. The kernel exposes its list of processes under a directory called /proc, which we can list:

bash-4.4$ ls /proc
1     asound     consoles   dma      fs      kallsyms   kpagecgroup  mdstat   mtrr      sched_debug  softirqs   sysvipc      version
201   buddyinfo  cpuinfo    driver   interrupts  kcore  kpagecount   meminfo  net       schedstat    stat       thread-self  version_signature
456   bus    crypto     execdomains  iomem       keys   kpageflags   misc     pagetypeinfo  scsi     swaps      timer_list   vmallocinfo
471   cgroups    devices    fb       ioports     key-users  loadavg      modules  partitions    self     sys        tty      vmstat
acpi  cmdline    diskstats  filesystems  irq         kmsg   locks        mounts   pressure      slabinfo     sysrq-trigger  uptime       zoneinfo

You won’t see exactly the same as this. But notice the numeric entries:

These are processes, each with its own PID. We can only see the processes inside this container.

PID 1 is the process which was started when the container started, so is normally the application process. We can check this:

bash-4.4$ cat /proc/1/cmdline
/bin/sh/opt/jboss/keycloak/bin/standalone.sh-Djboss.bind.address=172.17.0.3-Djboss.bind.address.private=172.17.0.3-c=standalone-ha.xml-b0.0.0.0

Yes, you can see that this is some script for running keycloak, together with some flags like -D jboss.bind.address172.17.0.3 (in this view, all the arguments are squashed together without spaces)

That script has spawned its own child process - in this case it’s 201 but yours may be different - which is the actual java process that runs the application.

bash-4.4$ cat /proc/201/cmdline
java-D[Standalone]-server-Xms64m-Xmx512m-XX:MetaspaceSize=96M-XX:MaxMetaspaceSize=256m...

Process 456 here is the bash shell we started, and process 471 was the ls command that we typed (and has now gone). Again, the pids you see will be different, except pid 1.

Now exit the shell, by typing:

exit

Container state: Upgrading a docker application

Now, suppose a new version of keycloak comes out in future, say 15.0.3. How would you upgrade it?

The answer may be very surprising. You simply destroy the old container, and start a new container from the new image. You never login to a container and perform upgrades inside the container!

However, this presents a problem. What happens to all the data which was written by the application inside the container? That will be lost.

There are two solutions here.

The first is to make the application “stateless” - so it doesn’t keep any important local data at all. It could, for example, connect to a database over the network, and store all its state in the database. (This is the recommended way to run keycloak in production).

The other option is to mount a “docker volume” inside the container. The docker volume is a subdirectory which persists even when the container is destroyed, and can be mounted in a new container.

To do this, you need to know which subdirectory within the container needs to be preserved. Again, you need to look at the container documentation.

Keycloak makes this hard to find (because they prefer that you use an external database), but it’s /opt/jboss/keycloak/standalone/data

So let’s start again. First, stop and destroy both the existing containers.

# docker stop keycloak keycloak2

# docker rm keycloak keycloak2

Now we’ll create a docker volume called keycloak-data

# docker volume create keycloak-data
keycloak-data

Now start keycloak again, in the background (-d), and this time with the volume attached:

# docker run -d -p 8443:8443 -v keycloak-data:/opt/jboss/keycloak/standalone/data -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak quay.io/keycloak/keycloak:15.0.2

Now we are safe. Whenever the application in the container writes files underneath /opt/jboss/keycloak/standalone/data then those files will go in the docker volume. To upgrade the application, you can delete the container and recreate with the same volume attached.

Do you want to test this?

We now have some state.

Stop and destroy the container:

# docker stop keycloak
# docker rm keycloak

Recreate it, attached to the same docker volume as before:

# docker run -d -p 8443:8443 -v keycloak-data:/opt/jboss/keycloak/standalone/data -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin --name keycloak quay.io/keycloak/keycloak:15.0.2

Go back into the web interface, and look at the realms section. Does it still have a realm called “Workshop”? It should!

You can keep track of your docker volumes using docker volume list:

# docker volume list
DRIVER    VOLUME NAME
local     keycloak-data

Managing image storage

You can see the images which have been downloaded to your system:

# docker images
REPOSITORY                  TAG       IMAGE ID       CREATED       SIZE
quay.io/keycloak/keycloak   15.0.2    bd845c0bf911   4 weeks ago   714MB

This image is quite large. You can try removing it, but it will fail because there are containers which are still using it:

# docker rmi quay.io/keycloak/keycloak:15.0.2
Error response from daemon: conflict: unable to remove repository reference "quay.io/keycloak/keycloak:15.0.2" (must force) - container 7cf852e94fc6 is using its referenced image bd845c0bf911

Now stop the keycloak container with docker stop:

# docker stop keycloak
keycloak
# docker ps
CONTAINER ID   IMAGE        COMMAND                  CREATED       STATUS       PORTS                                       NAMES
# docker ps -a
CONTAINER ID   IMAGE                              COMMAND                  CREATED          STATUS                      PORTS                                       NAMES
7cf852e94fc6   quay.io/keycloak/keycloak:15.0.2   "/opt/jboss/tools/do…"   27 minutes ago   Exited (0) 12 seconds ago                                               keycloak
# 

Even though it is stopped, the container is still using this image (it has a storage layer on top of this image). So to tidy up, you need to delete the container:

# docker rm keycloak
keycloak

Now you can delete the container image:

# docker rmi quay.io/keycloak/keycloak:15.0.2
Untagged: quay.io/keycloak/keycloak:15.0.2
Untagged: quay.io/keycloak/keycloak@sha256:b4a1ec2d93477ef32587bb29932e9567f967919a0a2d6cd54748060f90180b35
Deleted: sha256:bd845c0bf91114a6ebf2191a43cdcd1169f6fa81c7ff460ea3b178feeb9efbb9
Deleted: sha256:ee0c8020741cdff7c661563b3050b06350cac280468952b4472409271199673a
Deleted: sha256:2293744a3098b8ff5848d516ac1f65709af1b1e7646e54879328a4749487eb33
Deleted: sha256:ad7212ce593bbb61f246a3bef35dc4921cd244b7a6268c658bf59093f01a2cb3
Deleted: sha256:47c47b6fcf0b7a328bb5fc7bf9b66533702a0d345fa823c6107d3d2dc707a6b1
Deleted: sha256:54e42005468d41c980a6b5ec99544b942f856c6268750a09a5c4f1ed1226cf42

(You can see that the container image itself consisted of multiple layers)

In practice, you don’t have to delete unused images manually like this all the time. There is a special command docker image prune which removes all images that aren’t used by any containers. You can run it occasionally to reclaim storage space.