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.
From the board terminal, simply run the
docker command to get a list of supported Docker commands:
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:
Before trying to do more complex operations you should get familiar with some basic concepts.
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.
This section explains how to run a container either from the command-line or from Portainer.
From the command line, you have different commands that can be used to manipulate images, the most important ones are
docker pull and
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
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.
Now you have Portainer running on the board either:
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.
As you may have noticed in the output from previous commands, our
hello-world image was referenced as
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.
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:
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>
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:
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
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:
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.
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.
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.
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.
This section collects information about more advanced concepts.
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.
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.
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.
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:
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:
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.