Run and Manage Containers with the Command-line on Torizon
Introductionβ
This article explains how to run containers on Torizon OS using the command-line. Also, it presents how to evaluate the Container environment using Portainer.
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.
IDE Extensionβ
If you plan to write your own applications and create your own containers, consider using the Torizon IDE extension. It makes your life easier by providing several automated tasks to assist the development of containerized applications for embedded. Just hit F5
and the extension will deploy and run your application packaged inside a container. Also, the IDE extension integrates the Docker Visual Studio Code extension with your remote target device and facilitates container management tasks.
This is the recommended way to develop your own application if you use one of the programming languages supported by the extension.
Torizon Cloudβ
One important topic related to managing containers is how to update them to your devices, either during development or production. Torizon Cloud allows remote updates of not only containers but also the entire system.
Make sure to try it out: Get Started With the Torizon Cloud.
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 Torizon OS.
Prerequisitesβ
- Finish the Quickstart Guide.
- Basic knowledge of Torizon OS.
- System on Module with Torizon OS installed.
- Ability to connect to Torizon OS via SSH.
- Basic knowledge of the docker run command.
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:# 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.
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 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 from the command-line.
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 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
Digest: sha256:0ab9d25e9ac7b663a51afc6853875b2055d8812fcaf677d0013eba32d0bf0e0d
Status: Downloaded newer image for portainer/portainer-ce:2.0.0
docker.io/portainer/portainer-ce:2.0.0
If you are running the image Torizon OS 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
Digest: sha256:0ab9d25e9ac7b663a51afc6853875b2055d8812fcaf677d0013eba32d0bf0e0d
Status: Image is up to date for portainer/portainer-ce:2.0.0
docker.io/portainer/portainer-ce:2.0.0
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:
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.
Portainerβ
Now you have Portainer running on the board:
- Either by following the previous section.
- Or because you have installed Torizon OS 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 article.
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 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β
The tag 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 Torizon OS version. Learn more on the Torizon OS 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
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.
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
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
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>]
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
--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
--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.
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.
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
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
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 Torizon OS.
Environment Variablesβ
You may need to pass environment variables to the container that will be started. For example, Remote Access the Torizon OS 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 -e
or -env
option, or even pass the environment variable from a file. Learn more on the docker run reference.
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 Torizon OS.
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>
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:
You can delete only images not in use by any container, even if the container is stopped.
# docker rmi <tag/image id>
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
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)
Portainer steps
Containers can be removed also using Portainer's UI:
Using Portainer images list, you can delete only images not in use by any container, even if the container is stopped as follows:
Pruning is not a Portainer feature by design, though it have been accepted as a feature request, according to GitHub issue #904.
Creating your Own Portainer Templatesβ
As previously explained, the image Torizon OS 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.
Automatically Starting a Container with Torizon OSβ
Please refer to the How to Autorun an Application With Torizon OS for more information on this topic.