ROCK Images - Introduction

ROCK Images - Introduction

What are ROCKs?

Ordinary software packages can often be installed in a variety of different types of environments that satisfy the given packaging system. However, these environments can be quite varied, such as including versions of language runtimes, system libraries, and other library dependencies that the software was not well tested with.

Software containers address this by encapsulating both the software and the surrounding environment. Instead of installing and maintaining a collection of software packages, the user runs and maintains a single container, instantiated from a container image with the desired software already installed. The user relies on the provider of the container image to perform the necessary software testing and maintenance updates. There is a rich ecosystem of container providers thanks mainstream tools like Docker, and popular container registries like Docker Hub, Amazon ECR, etc., which make it easy for anyone to build and publish a container image. Unfortunately, with that freedom and flexibility invariably comes unreliability of maintenance and inconsistency of implementation.

The Open Container Initiative (OCI) establishes standards for constructing container images that can be reliably installed across a variety of compliant host environments.

Ubuntu’s LTS Docker Image Portfolio provides OCI-compliant images that receive stable security updates and predictable software updates, thus ensuring consistency in both maintenance schedule and operational interfaces for the underlying software your software builds on.

Container Creation and Deletion

Over the course of this tutorial we’ll explore deriving a customized Apache container, and then networking in a Postgres container backend for it. By the end you’ll have a working knowledge of how to set up a container-based environment using Canonical’s ROCKs.

First the absolute basics. Let’s spin up a single container providing the Apache2 web server software:

$ sudo apt-get update
$ sudo apt-get -y install docker.io
$ sudo docker run -d --name my-apache2-container -p 8080:80 ubuntu/apache2:2.4-22.04_beta
Unable to find image 'ubuntu/apache2:2.4-22.04_beta' locally
2.4-22.04_beta: Pulling from ubuntu/apache2
13c61b50dd15: Pull complete 
34dadde438e6: Pull complete 
d8e11cec95e6: Pull complete 
Digest: sha256:11647ce68a130540150dfebbb755ee79c908103fafbf805074eb6513e6b9df83
Status: Downloaded newer image for ubuntu/apache2:2.4-22.04_beta
4031e6ed24a6e08185efd1c60e7df50f8f60c21ed9961c858ca0cb6bb300a72a

This container, named my-apache2-container runs in an Ubuntu 22.04 LTS environment and can be accessed via local port 8080. Load the website up in your local web browser:

$ firefox http://localhost:8080

If you don’t have firefox handy, curl can be used instead:

$ curl -s http://localhost:8080 | grep "<title>"
<title>Apache2 Ubuntu Default Page: It works</title>

The run command had a number of parameters to it. The Usage section of Ubuntu’s Docker hub page for Apache2 has a table with an overview of parameters specific to the image, and Docker itself has a formal reference of all available parameters, but lets go over what we’re doing in this particular case:

$ sudo docker run -d --name my-apache2-container -e TZ=UTC -p 8080:80 ubuntu/apache2:2.4-22.04_beta

The -d parameter causes the container to be detached so it runs in the background. If you omit this, then you’ll want to use a different terminal window for interacting with the container. The --name parameter allows you to use a defined name; if it’s omitted you can still reference the container by its Docker id. The -e option lets you set environment variables used when creating the container; in this case we’re just setting the timezone (TZ) to universal time (UTC). The -p parameter allows us to map port 80 of the container to 8080 on localhost, so we can reference the service as http://localhost:8080. The last parameter indicates what software image we want.

A variety of other container images are provided on Ubuntu’s Docker Hub and on Amazon ECR, including documentation of supported customization parameters and debugging tips. This lists the different major/minor versions of each piece of software, packaged on top of different Ubuntu LTS releases. So for example, in specifying our requested image as ubuntu/apache2:2.4-22.04_beta we used Apache2 version 2.4 running on a Ubuntu 22.04 environment.

Notice that the image version we requested has _beta appended to it. This is called a Channel Tag. Like most software, Apache2 provides incremental releases numbered like 2.4.51, 2.4.52, and 2.4.53. Some of these releases are strictly bugfix-only, or even just CVE security fixes; others may include new features or other improvements. If we think of the series of these incremental releases for Apache2 2.4 on Ubuntu 22.04 as running in a Channel, the Channel Tags point to the newest incremental release that’s been confirmed to the given level of stability. So, if a new incremental release 2.4.54 becomes available, ubuntu/apache2:2.4-22.04_edge images would be updated to that version rapidly, then ubuntu/apache2:2.4-22.04_beta once it’s received some basic testing; eventually, if no problems are found, it will also be available in ubuntu/apache2:2.4-22.04_candidate and then in ubuntu/apache2:2.4-22.04_stable once it’s validated as completely safe.

For convenience there’s also a latest tag and an edge tag which are handy for experimentation or where you don’t care what version is used and just want the newest available. For example, to launch the latest version of Nginx, we can do so as before, but specifying latest instead of the version:

$ sudo docker run -d --name my-nginx-container -e TZ=UTC -p 9080:80 ubuntu/nginx:latest
4dac8d77645d7ed695bdcbeb3409a8eda942393067dad49e4ef3b8b1bdc5d584

$ curl -s http://localhost:9080 | grep "<title>"
<title>Welcome to nginx!</title>

We’ve also changed the port to 9080 instead of 8080 using the -p parameter, since port 8080 is still being used by our apache2 container. If we were to try to also launch Nginx (or another Apache2 container) on port 8080, we’d get an error message, Bind for 0.0.0.0:8080 failed: port is already allocated and then would need to remove the container and try again.

Speaking of removing containers, now that we know how to create generic default containers, let’s clean up:

$ sudo docker ps
CONTAINER ID   IMAGE                           COMMAND                  CREATED          STATUS          PORTS                                   NAMES
d86e93c98e20   ubuntu/apache2:2.4-22.04_beta   "apache2-foreground"     29 minutes ago    Up 29 minutes    0.0.0.0:8080->80/tcp, :::8080->80/tcp   my-apache2-container
eed23be5f65d   ubuntu/nginx:latest             "/docker-entrypoint.…"   18 minutes ago   Up 18 minutes   0.0.0.0:9080->80/tcp, :::9080->80/tcp   my-nginx-container

$ sudo docker stop my-apache2-container
$ sudo docker rm my-apache2-container

$ sudo docker stop my-nginx-container
$ sudo docker rm my-nginx-container

To be able to actually use the containers, we’ll have to configure and customize them, which we’ll look at next.

2 Likes

Maybe the paragraph explaining the channels could be replaced with a table, showing channel suffix names, and risk level? Then a short example explaining how publishing a new apache2 image would go through the channels.

1 Like

Also a great place to introduce docker ps and docker ps -a, just before showing the removal commands.

Thanks Andreas, I’ve added those points.

For the channel explanation, I kind of feel the better place for a table would be the Docker hub page. Honestly the main motivation for explaining them here is because the current table on the Docker hub page was a bit confusing on first read so I thought it might benefit from a prose description here. Some description of the image publishing process would be interesting but I fear it’d risk getting out of date quickly, especially with some of the current process ownership changes under way, so better to merely allude to it here and leave it to be separately documented. For similar reasons, the tutorial doesn’t delve into the technicals of building OCI images, though that’s also likely pertinent to the more advanced readers.

1 Like

Well, originally I thought we shouldn’t even mention these channels here, and I think the only reason we are doing it is because of the beta suffix in the tutorial, right? How about we just use the latest tag, or no tag at all (so it defaults to latest) to keep things simple? Then we don’t have to mention channels and publication workflows.

1 Like

Well, originally I thought we shouldn’t even mention these channels here

Yeah maybe. I’m curious when the images will be out of beta. I’d really prefer to reference the stable images (even using ‘latest’ feels like it could lead users towards unexpected volatility.)

1 Like

While I agree it’s useful to show how to pass environment variables to containers, the TZ one isn’t really useful/necessary here. I believe you can mention that you’re just passing it to illustrate the concept.

It’s better to use -s when invoking curl here (and elsewhere), just to prevent unnecessary output from polluting the example.

Another thing really worth mentioning (and using throughout the tutorial) is that you can run docker as a regular user, instead of running as root. All you need to do is add your user to the docker group.

Unfortunately this is not currently valid. The promotion process is still being figured out; I’m sure we will get somewhere very close to what you described above, but currently all of our tags point to the same image when they’re updated.

To illustrate what I’m saying, this is what currently happens when we build an image on LP (I’ll use apache2 as an example):

  • LP will upload the image to Dockerhub/AWS and tag it as ubuntu/apache2:2.4-22.04_edge.
  • We run our script that created new _beta and _candidate tags (as well as latest and edge), using the ubuntu/apache2:2.4-22.04_edge as a base.
  • Whenever we have to build a new version of the image, LP will overwrite the _edge tag that it creates, and we will overwrite the existing _beta, _candidate, latest and edge tags and make new ones that point to the _edge tag created by LP.

As I said, the ultimate goal is to have a promotion process that is more similar to what you described, but we’re not there yet.

Thanks, I’ve dropped the -e TZ=UTC from this initial example in interests of keeping it simple. The next example of docker run includes a discussion of environment variables and explains this one in particular, so as you say no use/need to include it in this first one.

I’ve updated to use curl -s

Another thing really worth mentioning (and using throughout the tutorial) is that you can run docker as a regular user, instead of running as root. All you need to do is add your user to the docker group.

I was deliberate in not using the group setting, but don’t have a firm opinion, so if anyone cares strongly and wants to change it go ahead. I thought it would seem less invasive to not modify group settings if someone was just kicking the tires, however when docker’s being run inside multipass that shouldn’t matter.

1 Like

Adding the user to the docker group has security implications. In fact, it’s almost like granting root privileges to this user, because they will then be able to launch privileged containers. Combined with bind mounts, this grants root on the host system (and I’m sure there are other ways).
This is also warned about in the upstream docs: https://docs.docker.com/engine/install/linux-postinstall/ and https://docs.docker.com/engine/security/#docker-daemon-attack-surface
Actually, we should probably mention this, regardless if we stick to using sudo or group membership.

1 Like