Pull and Run Containers
Brief Introduction into Podman
At the time of writing Docker is the most popular and most widely used container solution on the market. Due to licensing restrictions the use of Docker is often forbidden in a larger context if used without a proper license. In the following we are using Podman, an open-source alternative, instead of Docker, but most of the commands can be used with Podman and Docker alike. Please refer to the Setup section for Podman for installation instructions for your OS.
Verify the Podman installation
podman run hello-world
Output
Hello from Docker!
This message shows that your installation appears to be working correctly.
Basic Podman Commands
Now that everything is set up, it is time to issue the first podman
commands.
Let's pull our first image from Dockerhub.
As a start we want to pull this Python image.
podman pull python
Output
Resolved "python" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
Trying to pull docker.io/library/python:latest...
Getting image source signatures
Copying blob 3b1eb73e9939 done |
Copying blob b619962ff558 done |
Copying blob b1b8a0660a31 done |
Copying blob 48b8862a18fa done |
Copying blob c28d2e5e3fcb done |
Copying blob 0c01110621e0 done |
Copying blob 2aee75fc41a4 done |
Copying config f7711bfba6 done |
Writing manifest to image destination
f7711bfba6ee50a87befcd4f86f799bfb3af3ce9438bdaf937709efa56da7df3
The podman pull
command fetches the python
image from a registry and saves it locally
to our system.
Use the command podman images
to see a list of all images on your system.
podman images
Output
REPOSITORY TAG IMAGE ID CREATED SIZE
docker.io/library/python latest f7711bfba6ee 8 days ago 1.04 GB
Let's now run a container based on the python image.
To do that we use the command podman run
:
podman run python
Nothing really seemed to happen this time.
This is not a bug.
Many things were going on behind the scenes:
When you call the command podman run
, the podman client finds the image, loads up the container and runs a command inside the container.
We ran podman run python
without providing a command, and thus it directly exited again without producing output.
Let's try again by providing a command to podman run
.
podman run python python3 --version
Output
Python 3.13.4
We now ran the command python3 --version
in the container and, as expected, the python version number was printed out to the terminal.
Again, once the command finished the container exits.
The general pattern for running commands in a container is
podman run [options] image-name [command] [arguments]
.
Hopefully, you noticed that all of this happened pretty quickly.
Imagine you needed to boot up a virtual machine, run the command and destroy the VM.
That's the speed difference mentioned in the introduction.
The podman ps
command shows you all containers that are currently running.
podman ps
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
As expected, since no containers are running, we see a blank line.
Use podman ps -a
to get a more complete output:
podman ps -a
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0c60bdbb6a82 docker.io/library/python:latest python3 52 seconds ago Exited (0) 52 seconds ago keen_grothendieck
bcca3121881c docker.io/library/python:latest python3 --version 2 seconds ago Exited (0) 3 seconds ago upbeat_joliot
You might be wondering now if there is a way to run more than one command in a container.
Invoking the run
command with the -it
flag will give you an interactive tty session in the container.
Then you can run as many commands in the container as you want to, like in a normal bash
or Python interpreter.
podman run -it python python3
Output
Python 3.13.4 (main, Jun 11 2025, 02:31:21) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
podman run -it python bash
Output
root@43f5d561dcd3:/#
Interestingly, we also get an interactive Python interpreter by only running the command podman run -it python
.
Keep that in mind, we will see later why this works in that case.
So far, we have had no access to files from the local filesystem inside the container.
But you probably want to have exactly that.
With Podman, you can easily mount folders inside a container.
Supply the -v
option when running the podman run
command and as an argument pass the absolute path on the local filesystem on the left side and on the right side the absolute path inside the container.
Split both paths with a :
.
You can use -v
multiple times in one podman run
command.
The command below mounts the directory test
from the users home
-directory to the path /opt/test
inside the container.
podman run -v $HOME/test:/opt/test --rm -it alpine:latest sh
Output
Resolved "alpine" as an alias (/etc/containers/registries.conf.d/shortnames.conf)
Trying to pull docker.io/library/alpine:latest...
Getting image source signatures
Copying blob fe07684b16b8 done |
Copying config cea2ff433c done |
Writing manifest to image destination
/ #
/ # cd /opt/test
/ # touch foo
/ # exit
$ cd $HOME/test
$ ls
Output
foo
Package Permissions within the Container
Because of permissions and user IDs, it might happen, that you cannot access the files, that were written by the application within Podman.
Podman uses the root
-user if not specified differently somewhere else, which means, for example, that files, directories, and packages that were written or installed within the container would have user and group root
as the default.
This may result in an access denied
error message if you try to access those artifacts with a non-root user.
To avoid that, you have to pass you local user ID to Podman:
-u $(id -u ${USER}):$(id -g ${USER})
podman run --rm -it \
-u $(id -u ${USER}):$(id -g ${USER}) \
-v $HOME/test:/opt/test \
alpine:latest \
sh
What about the --rm
option though?
You might have already noticed that we can still see leftover containers from previous runs that even exited by running podman ps -a
.
Throughout this lesson you will run podman run
multiple times.
This will leave containers which in turn will occupy disk space.
It is necessary to regularly delete old containers from the disk.
To do that you can run the podman container rm
command.
Copy the container IDs from the output of podman ps -a
and paste them alongside the command.
podman container rm bcca3121881c 0c60bdbb6a82 36a2fc0eed60 543117f36209
Output
36a2fc0eed60
543117f36209
0c60bdbb6a82
bcca3121881c
This can be a repetitive task. You can delete a bunch of containers in one go as shown in the following command:
podman container prune
This command deletes all containers that are not running.
One last useful thing: Combine the podman run
command with the option --rm
as was shown before.
This will automatically delete the container once it exits from it.
But be careful, this is only useful, if you want to run the container only once.
It is definitely gone afterwards and will have to be re-created if you wish to run it again.
Volumes
We have already seen how to mount folders from the host to the container (these are called bind mounts). Volumes are the only way to have persistent data within a container and the reason why so many containers (like databases) use them. Because often it is not needed to access the files from the host system, we can let Podman manage this persistent storage.
podman volume ls
With this command we can list all existing volumes.
podman volume create test_volume
Here we created a volume called test_volume
.
podman volume ls
Output
DRIVER VOLUME NAME
local test_volume
Volumes are particularly useful because they are OS independent and fully managed by Podman. Some additional advantages of volumes:
- Volumes are easier to back up or migrate than bind mounts.
- Volumes can be more safely shared among multiple containers.
In the end, volumes are names for folders on the host (like /
or ~
).
We can see their location on the host, if we inspect a volume.
podman volume inspect test_volume
Output
[
{
"Name": "test_volume",
"Driver": "local",
"Mountpoint": "/home/hueser93/.local/share/containers/storage/volumes/test_volume/_data",
"CreatedAt": "2025-06-12T08:33:02.371878781+02:00",
"Labels": {},
"Scope": "local",
"Options": {},
"MountCount": 0,
"NeedsCopyUp": true,
"NeedsChown": true,
"LockNumber": 0
}
]
We can mount a volume like a folder from a host system by using its name on the left side of the :
in -v
argument.
podman run -it \
-v test_volume:/test \
alpine:latest \
touch /test/foo
After a volume is unmounted, we can use the following command to remove it.
podman volume rm test_volume
Note: All containers associated with a volume need to be removed beforehand.
Task: Run your own image
Task Description
Your goal in this exercise is to run a Jupyter notebook inside a container. You need to be able to open the Jupyter notebook in the browser of the host system and run Python commands inside. Delete the created container afterwards and make sure, that there are no leftover containers.
- Find the right image called
docker.io/jupyterhub/singleuser
(~300 MB) from Dockerhub and pull it. - Create a folder called
jupyter
on your local system. - Run a Jupyter container based on the image you just pulled. Bind the port
8888
tolocalhost
. Mount the folder created locally into the container. Note the hints below. - Open the Jupyter notebook in the browser of your host system by opening the link that
appears in the terminal:
http://127.0.0.1:8888/lab?token=...
- Run a simple calculation.
- Stop the container and delete the container image.
Hints: Use the option -p 127.0.0.1:8888:8888
to bind the Jupyter notebook port to your host
system, while the option -v $(pwd):/home/jovyan
mounts the current folder into the container.
Please also read the section about
user-related configurations
and add the following options: --user root -e NB_USER="jovyan" -e CHOWN_HOME=yes -w "/home/jovyan"
Solution
We use the image jupyterhub/singleuser from Dockerhub. Start the container, using the podman run
command.
podman run --name jupyter \
-p 127.0.0.1:8888:8888 \
-v $(pwd):/home/jovyan \
--user root \
-e NB_USER="jovyan" \
-e CHOWN_HOME=yes \
-w "/home/jovyan" \
docker.io/jupyterhub/singleuser:latest
Output
Trying to pull docker.io/jupyterhub/singleuser:latest...
Getting image source signatures
Copying blob ed2da93444d8 done |
Copying blob f03f49e66a78 done |
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob 06ac3029a007 done |
Copying blob 10bf4b5ce5bd done |
Copying blob 02d0c2faec92 done |
Copying blob eac6e0bb5c57 done |
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob 0ea87f6d444e done |
Copying blob 840146cd6907 done |
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob f487082da68d done |
Copying blob 2929654834f8 done |
Copying blob 7b59d9faa57a done |
Copying blob dd40919da564 done |
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob af3290429610 done |
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob 1bcaa076a606 done |
Copying blob 9abcb396bde3 done |
Copying blob 3cbc019f5cd1 done |
Copying blob 1b27d7959a26 done |
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob 4f4fb700ef54 skipped: already exists
Copying blob 4f4fb700ef54 skipped: already exists
Copying config 6cad4307aa done |
Writing manifest to image destination
Entered start.sh with args: start-notebook.py
Running hooks in: /usr/local/bin/start-notebook.d as uid: 0 gid: 0
Done running hooks in: /usr/local/bin/start-notebook.d
Ensuring /home/jovyan is owned by 1000:100
Running hooks in: /usr/local/bin/before-notebook.d as uid: 0 gid: 0
Sourcing shell script: /usr/local/bin/before-notebook.d/10activate-conda-env.sh
Done running hooks in: /usr/local/bin/before-notebook.d
Running as jovyan: start-notebook.py
Executing: jupyter lab
[I 2025-06-12 06:36:49.801 ServerApp] jupyter_lsp | extension was successfully linked.
[I 2025-06-12 06:36:49.802 ServerApp] jupyter_server_terminals | extension was successfully linked.
[I 2025-06-12 06:36:49.804 ServerApp] jupyterlab | extension was successfully linked.
[I 2025-06-12 06:36:49.806 ServerApp] nbclassic | extension was successfully linked.
[I 2025-06-12 06:36:49.807 ServerApp] notebook | extension was successfully linked.
[I 2025-06-12 06:36:49.808 ServerApp] Writing Jupyter server cookie secret to /home/jovyan/.local/share/jupyter/runtime/jupyter_cookie_secret
[I 2025-06-12 06:36:49.915 ServerApp] notebook_shim | extension was successfully linked.
[W 2025-06-12 06:36:49.925 ServerApp] WARNING: The Jupyter server is listening on all IP addresses and not using encryption. This is not recommended.
[I 2025-06-12 06:36:49.926 ServerApp] notebook_shim | extension was successfully loaded.
[I 2025-06-12 06:36:49.927 ServerApp] jupyter_lsp | extension was successfully loaded.
[I 2025-06-12 06:36:49.928 ServerApp] jupyter_server_terminals | extension was successfully loaded.
[I 2025-06-12 06:36:49.929 LabApp] JupyterLab extension loaded from /opt/conda/lib/python3.12/site-packages/jupyterlab
[I 2025-06-12 06:36:49.929 LabApp] JupyterLab application directory is /opt/conda/share/jupyter/lab
[I 2025-06-12 06:36:49.929 LabApp] Extension Manager is 'pypi'.
[I 2025-06-12 06:36:49.949 ServerApp] jupyterlab | extension was successfully loaded.
[I 2025-06-12 06:36:49.950 ServerApp] nbclassic | extension was successfully loaded.
[I 2025-06-12 06:36:49.952 ServerApp] notebook | extension was successfully loaded.
[I 2025-06-12 06:36:49.952 ServerApp] Serving notebooks from local directory: /home/jovyan
[I 2025-06-12 06:36:49.952 ServerApp] Jupyter Server 2.16.0 is running at:
[I 2025-06-12 06:36:49.952 ServerApp] http://localhost:8888/lab?token=59f6a3b6e875a681b79378c06f67c0529ac7a5797979f745
[I 2025-06-12 06:36:49.952 ServerApp] http://127.0.0.1:8888/lab?token=59f6a3b6e875a681b79378c06f67c0529ac7a5797979f745
[I 2025-06-12 06:36:49.952 ServerApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 2025-06-12 06:36:49.954 ServerApp]
To access the server, open this file in a browser:
file:///home/jovyan/.local/share/jupyter/runtime/jpserver-22-open.html
Or copy and paste one of these URLs:
http://localhost:8888/lab?token=59f6a3b6e875a681b79378c06f67c0529ac7a5797979f745
http://127.0.0.1:8888/lab?token=59f6a3b6e875a681b79378c06f67c0529ac7a5797979f745
[I 2025-06-12 06:36:49.961 ServerApp] Skipped non-installed server(s): bash-language-server, dockerfile-language-server-nodejs, javascript-typescript-langserver, jedi-language-server, julia-language-server, pyright, python-language-server, python-lsp-server, r-languageserver, sql-language-server, texlab, typescript-language-server, unified-language-server, vscode-css-languageserver-bin, vscode-html-languageserver-bin, vscode-json-languageserver-bin, yaml-language-server
When your container is running you can open your browser at http://127.0.0.1:8888/lab?token=...
.
After you closed the session you can remove it, using podman rm [name]
.
podman container rm jupyter
Output
jupyter
podman ps -a
Output
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES