Doing more with your Docker container

Article By : Mohammed Billoo

Setting up a server is a daunting task. The appropriate dependencies for a particular application must be satisfied. Sometimes, exact versions of interdependent software are required to satisfy interoperability...

The ubiquity of IoT devices has necessitated servers to allow “edge” devices to perform non-trivial tasks. However, setting up a server is a daunting task. The appropriate dependencies for a particular application must be satisfied. Sometimes, exact versions of interdependent software are required to satisfy interoperability. Also, once a server is configured and tested to work for a particular task, the exact configuration must be documented for future replication. Finally, dependencies of different applications and tasks may conflict and may require a completely separate server for isolation.

As an embedded software engineer, building and configuring a server to test out a particular feature on your edge device is mostly unnecessary. Instead, container technologies can be leveraged to set up a simple server to perform a single task. Multiple containers can be combined to form a set of virtual servers to interact with different applications running on IoT devices. In this article, we’re going to walk through some of the functionality that Docker provides in this regard.

This particular article is going to focus on Hawkbit, which is a server-side application that may be used to update a Linux-based edge device; SWUpdate is nominally run on the edge device to manage the update process. Since Hawkbit is run on a server, it would be ideal to run it in a Docker image, to avoid running into the issues outlined above.

Luckily, Docker provides a standard image of Hawkbit. A standard invocation of Docker to run a container with Hawkbit is the following:

$> docker run -d -p 8888:80 hawkbit/hawkbit-update-server

The above docker run command is passed the -d option, which instructs it to run in the background as a daemon. The option 8888:80, instructs Docker to forward all data that is being received on port 8888 of the host machine (i.e. the one that is running Docker) to port 80 of the Docker container. It is recommended to perform “port forwarding” from the host machine to the Docker container for security reasons. There are common ports that are used for certain web applications, and it is not recommended to open these ports on the host machine. If they are left open on the host machine, then an attacker can gain access to the machine using these ports. Instead, if a non-standard port is used on the host machine (such as 8888), the security threat is reduced.

Sometimes, due to a misconfiguration, the Docker container doesn’t perform as expected. For example, Hawkbit has a web interface for configuration. Due to the networking configuration of the host machine, the Hawkbit web interface may not be accessible. The following Docker commands can be used to view the log of the Docker container in real-time:

$> docker ps
$> docker logs -f <container ID>

The first command, docker ps, will list all of the running containers in a table with the following columns:

  • CONTAINER ID: A hash of the container, which will be used for any subsequent commands where the “container ID” is required.
  • IMAGE: The image name used to run the Docker container. In this example, this would be “hawkbit/hawkbit-update-server”.
  • COMMAND: The command that the container is running. In this example, this is the command used to run the hawkbit server.
  • CREATED: The amount of time elapsed since the container was created.
  • STATUS: The state of the container, including the time elapsed. In this example, the time displayed will be identical to that listed in under CREATED.
  • PORTS: The ports that are exposed to the container. In this example, “80” should be displayed.
  • NAMES: A unique name given to the particular instance.

Once the container ID is retrieved, using the docker ps command, it can be input to the docker logs -f command, which will show any log generated by the container in real-time. Another common command used to troubleshoot issues of a Docker container is:

$> docker attach <container ID>

This command will forward stdout from the container to your host machine, and it will forward stdin from your host machine to the container. In other words, it will be as if you’re sitting in front of the machine executed by the container and you have keyboard access to it and can see what’s being printed on the monitor.

Another common task with web applications is to provide HTTPS support, for security purposes. Recall that HTTPS encrypts traffic between a client and server, after an initial agreement of the encryption parameters. Nginx is an application that can provide HTTPS support, and can be combined with Hawkbit to provide FW/SW update over an encrypted connection. Since Docker provides a Nginx image on their hub, it can be combined with the Hawkbit Docker container image to provide a singular solution. This can be accomplished with Docker Compose. Docker Compose allows configuration of multiple Docker containers in a single file. These Docker containers represent distinct machines that are connected to one another on the internal Docker network.

The configuration file is usually named “docker-compose.yml”  and has key-value pairs that define a particular configuration. The following is an example of a sample file with both Hawkbit and Nginx:

        image: hawkbit/hawkbit-update-server
        restart: always
            -  “8888:80”
            NAME: “hawkbit-test”
        image: nginx
            - “8443:443”

We’re going to focus our attention on the structure and format of the configuration file, since the specifics of the Nginx configuration is outside of the scope of this article. Every “Docker Compose file”, which is what the configuration file is usually referred to as, starts with “services”, since the purpose of a container is to provide some sort of service. Each container is then identified by a particular name. It is important to note that the indentations are important, as Docker Compose requires the file to be formatted a certain way. Then, under each individual container, a set of key-value pairs define the required configuration parameters for the container. In the above example, under hawkbit, we have the following key-value pairs:

  • image: Instructs Docker which container image it should use.
  • restart: Instructs Docker to restart the container if it is stopped for any reason.
  • ports: Instructs Docker how to forward certain ports from the host machine to the container (as discussed previously).
  • labels: The instance name that Docker should use when launching the container.

Under the nginx container, the volume key-value pair is present, to instruct Docker to mount a particular location on the host machine to the Docker container (this is identical to the -v option that is given to the docker run command, which was discussed in the previous article). Finally, to launch these containers, the following command should be executed in the same location as the configuration file:

$> docker-compose up

And that’s it! Two docker containers should be up and running.

In summary, Docker provides additional mechanisms to enable appropriate security practices when launching and running a Docker container, and to support debugging  issues that may arise when using a Docker container. Finally, Docker Compose is a useful tool that enables launching multiple containers using a single configuration file. All of these tools are invaluable when testing applications for IoT-based systems.

— Mohammed Billoo is Founder of MAB Labs, LLC.

Leave a comment