Search by Tags

Run and Manage Docker Container on Torizon

 

Article updated at 21 Jul 2020
Subscribe for this article updates

Introduction

This article explains how to run containers on TorizonCore using both the browser-based Docker manager Portainer interface and the command-line.

Portainer comes installed and enabled by default in the image TorizonCore with evaluation containers. It is accessible either from a local display or from a web-browser, given that the board is connected to the same network that your PC.

In contrast to the Quickstart Guide, this article has in-depth explanations of how things work and more complex examples. In addition, throughout our documentation we always use command-line examples instead of Portainer for practical purposes, therefore this article helps you to understand how to translate those commands to the Portainer UI.

This article complies to the Typographic Conventions for Torizon Documentation.

Prerequisites

How to Run Your First Docker Container

From the board terminal, simply run the docker command to get a list of supported Docker commands:

# docker

To start the first container and check that everything works as expected you should connect your device to the internet and type:

# docker run --rm hello-world

If you get the following output your system is ready to run containers:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4ee5c797bcd7: Pull complete 
Digest: sha256:9572f7cdcee8591948c2963463447a53466950b3fc15a247fcad1917ca215a2f
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (arm32v7)
 3. The Docker daemon created a new container from that image that runs the executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

Actually, with that simple command, you did more than just running a container.

Under the hood:

  • Docker downloaded the hello-world image from Docker Hub.
  • Docker created a container based on the hello-world image.
  • Docker ran the container redirecting its output to the board console.
  • Docker removed the container once it terminated (--rm).

Basic Concepts

Before trying to do more complex operations you should get familiar with some basic concepts.

Images and Containers

When you want to run a container you need an image, sometimes also referred to as container image. The image defines the filesystem and environment used to run your container. A container can’t exist without an image. Luckily you can find plenty of pre-built images - mostly on Docker Hub - and also build your own ones.

To run a container the image it’s based on must be on the local system. If the image is not there (as our hello-world image in the previous sample), Docker will download (pull) it from a registry. The concept of container registries will be discussed in detail later. The important point here is that no container exists without an image, the image defines what are the “contents” of our container.

When a new container is created Docker allocates some storage for the modifications that the container will make to the filesystem and starts a process that runs inside the container “sandbox”. Notice that the original image is not altered, it is read-only.

The behavior of the container and what’s exposed from and into its sandbox can be defined when the container is started, both using the command-line or other tools like Portainer or docker-compose.

When the container main process terminates, the container stops its execution but changes it made to its filesystem aren’t lost. The container can be restarted by re-executing the main process, but most of the time those changes will not serve any specific purpose, so you can destroy the container to free those resources. Through our documentation you will often see a docker run command with the --rm flag, meaning that the container is destroyed after the main process exits and all changes are lost.

Containers are transient by design, they are not supposed to be used to store information inside their own copy of the image filesystem. We are going to see how to store permanent data independently of a container lifetime later in this article.

How to Run a Container

This section explains how to run a container either from the command-line or from Portainer.

Command-line Interface

From the command line, you have different commands that can be used to manipulate images, the most important ones are docker pull and docker run.

The docker pull command can be used to download an image. If the image is already on your device, Docker will check if there is an updated version and download it. Since images are organized in layers, the download may not require transferring the full size of the new image, but just the layers that have been changed.

For example the following command line will download Portainer:

# docker pull portainer/portainer
Using default tag: latest
latest: Pulling from portainer/portainer
d1e017099d17: Pull complete 
454a1d147646: Pull complete 
Digest: sha256:026381c60682b82a863f0c3737a9b4a414beaddd4cf050477a7749ff5ac61189
Status: Downloaded newer image for portainer/portainer:latest
docker.io/portainer/portainer:latest

If you are running the image TorizonCore with evaluation containers, Portainer container should be already on the device and no download will be performed - unless an updated version of it is available:

# docker pull portainer/portainer
Using default tag: latest
latest: Pulling from portainer/portainer
Digest: sha256:026381c60682b82a863f0c3737a9b4a414beaddd4cf050477a7749ff5ac61189
Status: Image is up to date for portainer/portainer:latest
docker.io/portainer/portainer:latest

The docker run command can be used to start a container based on a specific image and configure its behavior. If the image is not already on the device, Docker will try to download it (basically performing the same operation performed by the docker pull command). An important difference is that docker run won’t check for updates if the image is already on the device.

If you want to run Portainer you can execute the command below. We will discuss some of the parameters in the following paragraphs:

Warning: if Portainer is already running on your device, you may get an error message when trying to run this command, since the port 9000 will be already in use.

# docker run -v /home/torizon/portainer:/data -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
2020/02/20 06:58:39 server: Reverse tunnelling enabled
2020/02/20 06:58:39 server: Fingerprint 48:ef:4a:a6:cf:b3:3e:b2:09:ec:db:61:34:6c:b5:2a
2020/02/20 06:58:39 server: Listening on 0.0.0.0:8000...
2020/02/20 06:58:39 Starting Portainer 1.23.1 on :9000
2020/02/20 06:58:39 [DEBUG] [chisel, monitoring] [check_interval_seconds: 10.000000] [message: starting tunnel management process]

If you navigate to http://<your device IP>:9000 you will see the Portainer UI. After configuring a password for the admin, you will see the main Portainer screen.

Portainer

Now you have Portainer running on the board either:

  • By following the previous section, or;
  • Because you have installed TorizonCore with evaluation containers, which comes with Portainer pre-installed and enabled by default.

The basics of how to run a container from Portainer are well covered in the Quickstart Guide - Starting and Managing Containers with Portainer, make sure to go through it.

Tags

As you may have noticed in the output from previous commands, our hello-world image was referenced as hello-world:latest.

Each Docker image has a repository name (hello-world) and then it may have one or more tags attached. If no tag is specified, latest will be used by default. Tags can be used to provide versioning for the images. Multiple tags may be attached to a single image and tags can also be moved between different versions of the same image (for example the latest tag will be attached to the freshly built image by default).

Each image is also uniquely identified by a SHA256 id, this can be used to ensure that you are referencing a specific image even if the original tags have been moved.

Also, containers have a name and a unique id. Docker will generate a random name if you don’t specify it.

Interactive vs. Daemonized Containers

Most of the time containers will run in the background, providing services and not interacting directly with the user via the console. Usually, when a container starts it generates some output on the console and then detaches from it to keep running in the background. This is the default behavior when you start a container. Some containers, like Portainer, may not do that and so will keep your console in use.

You may force a container to move immediately to the background by starting it in daemon mode by passing the -d option to the docker run command.

Lately, containers have become also a quite popular way to run tools and applications without installing software on a specific machine/device main OS. In this case, the software running inside the container may need or want to interact, or provide feedback, through the same console used to start it, at least after it has completed its initialization.

Interactive containers may be executed by passing the --it parameter to the docker run command. It guarantees that the application inside the container will run like any other process on that same console.

The output of a container (stdout and stderr) is not lost when it terminates and you can retrieve it with the following command:

# docker logs <container name/id>

Please notice that this works even if you just type the first digits of the container id, as long as the part you typed is long enough to uniquely identify one of the containers

If you are using Portainer, you can click on the Logs button once you select a container:


  • See container logs from Portainer

    See container logs from Portainer

Even if you started your container interactively you may want to move it to background after some time, for example after you checked that all startup operations completed successfully. You can do that by pressing Ctlr+P and then Ctrl+Q on the console. This will detach the container from your console, keeping it running in the background. On the other side, if you need to attach to a container running in the background, whether you started it daemonized or detached from it later, you can use the docker attach command:

# docker attach <container name/id>

Command and Entry Point

The purpose of a container is to run one or more processes. Usually, the main process is defined by the image and can be overridden on the Docker command-line or via Portainer UI.

For example, you can run our default base container and start ls instead of the shell that is usually started:

# docker run torizon/arm32v7-debian-base:buster ls -la
total 68
drwxr-xr-x   1 root root 4096 Feb 20 16:22 .
drwxr-xr-x   1 root root 4096 Feb 20 16:22 ..
-rwxr-xr-x   1 root root    0 Feb 20 16:22 .dockerenv
drwxr-xr-x   2 root root 4096 Jan 30 00:00 bin
drwxr-xr-x   2 root root 4096 Nov 10 12:17 boot
drwxr-xr-x   5 root root  320 Feb 20 16:22 dev
drwxr-xr-x   1 root root 4096 Feb 20 16:22 etc
drwxr-xr-x   1 root root 4096 Feb 17 10:02 home
drwxr-xr-x   1 root root 4096 Feb  4 18:21 lib
drwxr-xr-x   2 root root 4096 Jan 30 00:00 media
drwxr-xr-x   2 root root 4096 Jan 30 00:00 mnt
drwxr-xr-x   2 root root 4096 Jan 30 00:00 opt
dr-xr-xr-x 134 root root    0 Feb 20 16:22 proc
drwx------   2 root root 4096 Jan 30 00:00 root
drwxr-xr-x   3 root root 4096 Jan 30 00:00 run
drwxr-xr-x   2 root root 4096 Jan 30 00:00 sbin
drwxr-xr-x   2 root root 4096 Jan 30 00:00 srv
dr-xr-xr-x  12 root root    0 Feb 20 16:22 sys
drwxrwxrwt   1 root root 4096 Feb  4 18:23 tmp
drwxr-xr-x   1 root root 4096 Jan 30 00:00 usr
drwxr-xr-x   1 root root 4096 Jan 30 00:00 var

In Portainer, to provide the command on container creation you must enable the Advanced mode, then in the Command option under Commands and logging you pass the command:


  • Passing a command to a container from Portainer

    Passing a command to a container from Portainer

Usually commands are executed invoking a shell (by default /bin/sh) that can do additional command-line parsing or setup the environment for the command. This can be used to start a custom command and provide parameters directly on the Docker command-line, or the Command field in Portainer. Many tool use this approach, including Portainer.

So, if you run:

# docker run portainer/portainer --help

You will get a list of the supported command-line parameters for Portainer:

usage: portainer [<flags>]

Flags:
      --help                     Show context-sensitive help (also try
                                 --help-long and --help-man).
      --version                  Show application version.
  -p, --bind=":9000"             Address and port to serve Portainer
      --tunnel-addr="0.0.0.0"    Address to serve the tunnel server
      --tunnel-port="8000"       Port to serve the tunnel server
  -a, --assets="./"              Path to the assets
  -d, --data="/data"             Path to the folder where the data is stored
  -H, --host=HOST                Endpoint URL
      --external-endpoints=EXTERNAL-ENDPOINTS  
                                 Path to a file defining available endpoints
      --no-auth                  Disable authentication
      --no-analytics             Disable Analytics in app
      --tlsverify                TLS support
      --tlsskipverify            Disable TLS server verification
      --tlscacert="/certs/ca.pem"  
                                 Path to the CA
      --tlscert="/certs/cert.pem"  
                                 Path to the TLS certificate file
      --tlskey="/certs/key.pem"  Path to the TLS key
      --ssl                      Secure Portainer instance using SSL
      --sslcert="/certs/portainer.crt"  
                                 Path to the SSL certificate used to secure the
                                 Portainer instance
      --sslkey="/certs/portainer.key"  
                                 Path to the SSL key used to secure the
                                 Portainer instance
      --sync-interval="60s"      Duration between each synchronization via the
                                 external endpoints source
      --snapshot                 Start a background job to create endpoint
                                 snapshots
      --snapshot-interval="5m"   Duration between each endpoint snapshot job
      --admin-password=ADMIN-PASSWORD  
                                 Hashed admin password
      --admin-password-file=ADMIN-PASSWORD-FILE  
                                 Path to the file containing the password for
                                 the admin user
  -l, --hide-label=HIDE-LABEL ...  
                                 Hide containers with a specific label in the UI
      --logo=LOGO                URL for the logo displayed in the UI
  -t, --templates=TEMPLATES      URL to the templates definitions.
      --template-file="/templates.json"  
                                 Path to the templates (app) definitions on the
                                 filesystem

Mount Folders and Devices

By default, containers run on a filesystem that is a copy of the one provided by the original image used to start them, and any change is stored permanently in the container until it is destroyed. This filesystem is normally not accessible from outside the container and so storing data there is not very useful. If you need to store permanent data that can be shared between different containers, between multiple executions of the same image or between the container and the host OS, you can bind-mount a folder or a file from the host filesystem into the container.

To bind-mount a folder or a file you can use the -v option passing a local path, followed by a path inside the container and an optional access mask (for example: r for read only or rw for read-write, that is the default).

On the command-line:

# docker run -v /home/torizon:/torizon torizon/arm32v7-debian-base:buster ls -la /torizon

The example above will list files in the Torizon’s user home folder.

You can pass bind-mounts via Portainer using the Volumes section of the Add container form:


  • Bind-mount from Portainer

    Bind-mount from Portainer

Sometimes you may not care about where your data is going to be stored on the host system, but you still want some permanent storage that could "survive" a specific container instance. In this case, you may want to use volumes. Volumes can be created by Docker and mounted as folders inside containers providing an easy way to store permanent data without having to bind that to a specific path on the host filesystem.

To use a volume you have to create it first. On the command-line:

# docker volume create my_very_important_data

In portainer, go to on the Volumes page on the lateral bar and click Add volume.


  • Create a volume on Portainer

    Create a volume on Portainer

Once your volume is created you can mount its contents inside a container using the -v argument and passing the volume name instead of a local path.

Since on *nix based systems "everything is a file", you can also share sockets and other file-like objects that could be used to make different applications communicate across container boundaries. That would apply also for devices, but in this case, instead of sharing a file, you may want to have the same virtual file entry to be re-created inside the container filesystem using mknod. In this way, a device can be exposed inside a container and behave exactly like the original device on the host OS.

To pass devices you can use the command line:

# docker run --device /dev/gpiochip0 torizon/arm32v7-debian-base:buster

In Portainer, you can add devices from the Runtime & Resources section of the Add container form.


  • Add device to container from Portainer

    Add device to container from Portainer

To learn more about volumes and bind-mounts, you can read the Docker articles Manage data in Docker, Use volumes and Use bind mounts.

Networking

Docker can allow you to create complex network structures, connecting different containers and keeping some segment private, among other options. We are not going to discuss this whole topic in this article, referencing users to the Docker networking documentation, but it can be useful to know some basic concepts that will help you organizing your architecture and exposing services to the right "audience".

One thing you may want to do is to expose ports to the network where your device is connected. By default, no ports are exposed by containers. You can define which ports are exposed in an image (this makes sense because it will also contain the server exposing those services), but you’ll also have to enable those port at runtime when starting your container. Ports can be redirected so, for example, a server that is bound to port 80 inside a container may actually be accessible on port 8080 on the device.

You can also create private networks and connect containers to them. In this way, containers can talk to each other without exposing any service to the outside.

In some scenarios you may want the container to run directly on top of the host-OS network stack, sharing the same IP addresses and exposing all its ports by default. You can do this by running the container network in host mode.

For some more information about networking you can check our article Networking with TorizonCore.

Docker Registry and Docker Hub

When you perform a pull operation, Docker connects to a server where images are stored and indexed. By default, this server is Docker Hub. Images can be freely downloaded from the hub and you need to login only to pull new images or new versions of your images. It’s very convenient and can be accessed by any device connected to the internet.

In some scenarios you may not want to have your images accessible on the internet, your device is not connected to the internet at all or has a slow or expensive connection that makes the downloading image too slow or too expensive. In this case, you can run your own registry, on a local server or in the cloud, and point your clients to it for downloads. You just need to prepend the URL of your registry to the image name and tag.

There is plenty of documentation about how to run your own container registry and some configuration aspects may be specific of your usage scenario. You can start by reading Docker's articles Docker registry, Deploy a registry server and Configuring a registry.

Advanced Concepts

This section collects information about more advanced concepts.

Docker-compose

We have seen how to pull an image, how to run it, how to share resources between the host OS and the applications running inside a container.

In real-world scenarios, you may want to run multiple containers that communicate between themselves and to the outside world to provide different services.

Instead of starting them individually, or with a script if you use command-line, you can create a docker-compose.yml file. This will let you define what containers you want to run, what folders/volumes should be mounted, what port they will expose and how they are going to be started.

Toradex has a dedicated article for docker-compose usage named Using Multiple Containers with TorizonCore.

Portainer Templates

The image TorizonCore with evaluation containers can be used to test many different demos. To see and use the templates pre-provisioned into the image, you can go to the App Templates section in Portainer and click on one of the templates.


  • Start a template using Portainer

    Start a template using Portainer

To use your own templates, specify the URL of App Templates in Settings section of Portainer UI or as a parameter from command-line using "--template" flag.
To read more about defining your own templates, visit Portainer templates.

Removing Containers, Images and System Prune

After some time experimenting with containers, pulling images and updating them, you may want to recover some storage space. You probably have old images with no tags attached, containers that terminated their execution, etc.

Containers can be automatically removed if you specify the --rm flag on startup. If you didn’t, for example because you may need to check the container’s logs or you wanted to have a chance to restart it, you can use the docker rm command:

# docker rm <container name/id>

Containers can be removed also using Portainer’s UI:


  • Remove a container using Portainer

    Remove a container using Portainer

Actually, container instances should not use too much space. As we have explained the only space taken is for the logs and changes to their filesystem that should be minimal. what can take some storage space are container images. If you no longer need an image you can delete it using its tag or ID:

# docker rmi <tag/image id>

You can delete only images not in use by any container, even if the container is stopped. The same operation can be performed using Portainer’s images list:


  • Remove an image using Portainer

    Remove an image using Portainer

You can, of course, delete those resources manually as explained above, but Docker provides an easy way to do this, removing all non-used resources at once. This operation is called pruning and will remove stopped containers, images that are not attached to any tag, unused network, and the cache used during build (each build step generates an image).

From the command-line:

# docker system prune
WARNING! This will remove:
  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N] y
Deleted Containers:
8ebfa9ebc213f26aa3ff02babf3d46514f7b9fb5cb762e26d93ccef865a3c484
65f44c85eb51e8ebe4855c48bddc1bf8b16def652fd263a30640ae586b63b060
73b69985562087727cf9d69f4d4f9b2d01cc9e859bec1ed1484fdf25c466e82b
53b5254fd4a8ec10ecf4cf1800985917344b2c3e5166520aa41257ad0e1f60d0
1fe515af639f7ee8ad6e38d3c392081c9662033dae276ff1e26b290b21d83066
b8640858c5140aad82553eb647a24531efdff6af8ad0f87785bae2573db9cb62
db2e3fad64461bbf3c363f26c3b328e2b98165479114d6a56db508851e2d76d9
d288c6a8f5b949a9cce777f64b2959f144248a453c118bd5226958822b3bd726
fe8027c39508dc042456f870a9a4b3f859b60467038805569b2d12bbed8a0d9e
290a72161ebfe89b20ec1d647bd3e1c931680969d8f0865b7e0a7482ba4483de
3a933208511c295e5aeaf22f409cb5bf39d0f29722492448ef68d03c29ec7cbc
d28f5a94d66413cdf005af3287c38df9430a2263637d194c6367006038a1b97d
d11c527f9127a2a8271dccc129d6d90430b5931e318f68f410e6ab72cf8d292a
2add264af73127b872786ed8d1e6010ad27445b751ce3c70830e852545ee5b52
cbc1bac673ff158f341eb3c9fa8f096291f658c1a1740548b5ba0d28cbef750f
83d634a83d7f8b26fbc84ac74a7b656fe5e7dbdad4aa3e026e9ac075b05e73f9
5e44b7e1f968549c7a86e723ee2c65bdb78f7a2a82d92943ffbd5114a9eb0426
70f010b4684091d6159f2e58963ee05a66ef8a8f460ce53d0d92a09f8f2e4f9f
39d3fe4fea6ebabb93de435bb70ad27e0193b8a3dde301fa8f6cecb803ead4f1
4776d574178852d1b504d68e29a60916ea0dd5f45dc04f0c059f9dcadf4a7b59
52ef813e23aafbde66e954082f37d5a01edbca048edd312f677833a8d10e27a7
f218ffd88bf910dff4da1777c6255cdbc2ac051c9feb93ed8382f85f8ce5faf8

Deleted Networks:
torizon_backend
torizon_frontend

Total reclaimed space: 27.81MB

Pruning is not a Portainer feature by design, though it seems to have been accepted recently as a feature request, according to GitHub issue #904.