Run and Manage Containers with Portainer and the Command-line on Torizon
This article explains how to run containers on TorizonCore using both the browser-based Docker manager Portainer interface and the command-line.
It includes running the containers from the List of Container Images for Torizon, such as the Debian Containers for Torizon, and many more containers from the Docker Hub or the ones you build by yourself.
The Portainer application 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. While some developers are comfortable using the command-line, we choose Portainer as a graphical alternative that provides a nice easy-to-use experience.
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.
If you plan to write your own applications and create your own containers, consider using our IDE extensions. They will abstract away the concept of containers and you will focus on writing your own application. Just hit
F5 and the extensions will deploy and run your application packaged inside a container:
This is the recommended way to develop your own application if you use one of the programming languages supported by the extensions.
When it comes to container management, Visual Studio Code has great integration. The Visual Studio Code Extension for Torizon provides a great experience, and the official Docker Extension can give you more options.
Torizon Platform Services
One important topic related to managing containers is how to update them to your devices, either during development or production. Torizon Platforma Services allows remote updates of not only containers but also the entire system. Make sure to try it out:
(Get Started With the Torizon Platform)[https://app.torizon.io/]Get Started With the Torizon Platform
Deploying Your Own Container Images
This article assumes that there are pre-existent container images deployed to an online container registry, such as Docker Hub. To learn more about how to deploy your own image, read the article Deploying Container Images to TorizonCore.
This article complies to the Typographic Conventions for Torizon Documentation.
- Finish the Quickstart Guide.
- Basic knowledge of TorizonCore.
- Computer on Module with TorizonCore installed.
- Ability to connect to TorizonCore via SSH.
- Basic knowledge of the docker run command.
Running Demo Containers using Portainer Templates
The image TorizonCore with evaluation containers can be used to easily 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.
Portainer is displayed on the default video output, usually VGA or HDMI - see Setting up Displays with Torizon for more information - or alternatively make sure your PC is in the same LAN as the board, open a browser, and navigate to
With Portainer opened, explore it by going to the App Templates section in Portainer and select a demo template.
How to Run Your First Docker Container from the Command-Line
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
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.
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:
For more examples and ideas, visit:
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
- Docker ran the container redirecting its output to the board console.
- Docker removed the container once it terminated (--rm).
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.
Toradex recommends that you use our Debian Containers for Torizon, mostly because we have already solved many things for you: the graphics stack is ready-to-use, granular permissions to hardware make your system more secure, you can use Debian packages built by Toradex out-of-the-box, if you use various containers with the same base you'll optimize flash storage, among other benefits.
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.
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 2.0.0:
# docker pull portainer/portainer-ce:2.0.0
2.0.0: Pulling from portainer/portainer-ce
d1e017099d17: Pull complete
927254088368: Pull complete
Status: Downloaded newer image for portainer/portainer-ce:2.0.0
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-ce:2.0.0
2.0.0: Pulling from portainer/portainer-ce
Status: Image is up to date for portainer/portainer-ce:2.0.0
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:
if Portainer is already running on your device, you may get an error message when trying to run this command, since the port 8840 will be already in use.
# docker run -v /home/torizon/portainer:/data -p 8840:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer-ce:2.0.0
2020/12/14 14:06:34 server: Reverse tunnelling enabled
2020/12/14 14:06:34 server: Fingerprint df:f9:2c:b3:e7:0f:c1:ea:b7:24:d4:a8:50:b3:17:03
2020/12/14 14:06:34 server: Listening on 0.0.0.0:8000...
2020/12/14 14:06:34 Starting Portainer 2.0.0 on :9000
2020/12/14 14:06:34 [DEBUG] [chisel, monitoring] [check_interval_seconds: 10.000000] [message: starting tunnel management process]
If you navigate to
http://<your device IP>:8840 you will see the Portainer UI. After configuring a password for the admin, you will see the main Portainer screen.
Another very useful command is
docker ps, since it lists all running containers. If you pass the argument
-a as in
docker ps -a, it also lists stopped containers.
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.
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 as long as the container image provides such a tag. 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.
Debian Containers for Torizon Tags
latest is purposefully not used in our Debian Containers for Torizon. That way, we can ensure that a compatible version of a container image is being run on a specific TorizonCore version. Learn more on the TorizonCore Containers Tags and Versioning.
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:
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/debian:$CT_TAG_DEBIAN ls -la
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 tools use this approach, including Portainer.
So, if you run:
# docker run portainer/portainer-ce:2.0.0 --help
You will get a list of the supported command-line parameters for Portainer:
usage: portainer [<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
--edge-compute Enable Edge Compute features
--no-analytics Disable Analytics in app (deprecated)
--tlsverify TLS support
--tlsskipverify Disable TLS server verification
Path to the CA
Path to the TLS certificate file
--tlskey="/certs/key.pem" Path to the TLS key
--ssl Secure Portainer instance using SSL
Path to the SSL certificate used to secure the
Path to the SSL key used to secure the
--snapshot-interval="5m" Duration between each endpoint snapshot job
Hashed admin password
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.
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/debian:$CT_TAG_DEBIAN 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/debian:$CT_TAG_DEBIAN
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.
You may need to pass environment variables to the container that will be started. For example, Remote Access the TorizonCore GUI Using VNC or RDP is activated using an environment variable. You may also, for instance, set the graphical back-end for Qt or GTK.
From the command-line, you can use the
-env option, or even pass the environment variable from a file. Learn more on the docker run reference.
From Portainer, when starting a container you can add environment variables from the the Env tab:
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.
Creating your Own Portainer Templates
As previously explained, 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 the 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:
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
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.
Be careful with the commands below, they can't be undone!
How to kill all running containers:
docker kill $(docker ps -q)
How to remove all stopped containers:
docker rm -f $(docker ps -a -q)
How to remove all images:
docker rmi -f $(docker images -q -f dangling=true);docker rmi -f $(docker images -q)
Automatically Starting a Container with TorizonCore
Please refer to the How to Autorun an Application With TorizonCore for more information on this topic.