---
# System prepended metadata

title: Podman
tags: [container, Podman]

---

# Podman

###### tags: `container`, `Podman`

目前比較多人用的是 [docker](https://www.docker.com/) 工具來進行 container 管理，但我想用 [Podman](https://podman.io/)。原因是因為 Open Source，且 Podman 是 [Open Container Initiative](https://opencontainers.org/) (OCI) 提供的工具。
有能力的話，可以自己加功能、修 Bug，並送回 upstream。但我還不會 [Go](https://golang.org/) 就是了 :P

* Podman 和 Docker 基本概念、指令可以互通使用
* 可以使用相同的 image registry，例如 [docker.io/library](https://hub.docker.com/) 
* Docker 要先用 systemctl 起 docker.service，再下指令；但 Podman 直接是指令 base，可以直接下指令使用。 

## 安裝

```shell=
pacman -S podman
```

## Pull image

```shell=
$ podman pull docker.io/library/alpine:3.14
$ podman images
REPOSITORY                TAG           IMAGE ID      CREATED       SIZE
docker.io/library/alpine  3.14          021b3423115f  7 days ago    5.87 MB
```

## Have the shell in a container

```shell=
$ podman run -it --name test docker.io/library/alpine:3.14 sh
/ #
$ podman ps -a
CONTAINER ID  IMAGE                          COMMAND     CREATED         STATUS                     PORTS       NAMES
649b5756f07f  docker.io/library/alpine:3.14  sh          25 seconds ago  Exited (0) 24 seconds ago              test
$ podman start test
test
$ podman attach test
/ #
```

## Build an image with Dockerfile

```shell=
$ cat python-dockerfile 
FROM docker.io/library/alpine:3.14

RUN apk add --no-cache python3

$ podman build -t mypython -f python-dockerfile 
STEP 1: FROM docker.io/library/alpine:3.14
STEP 2: RUN apk add python3 --no-cache
fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/main/x86_64/APKINDEX.tar.gz
fetch https://dl-cdn.alpinelinux.org/alpine/v3.14/community/x86_64/APKINDEX.tar.gz
(1/13) Installing libbz2 (1.0.8-r1)
(2/13) Installing expat (2.4.1-r0)
(3/13) Installing libffi (3.3-r2)
(4/13) Installing gdbm (1.19-r0)
(5/13) Installing xz-libs (5.2.5-r0)
(6/13) Installing libgcc (10.3.1_git20210424-r2)
(7/13) Installing libstdc++ (10.3.1_git20210424-r2)
(8/13) Installing mpdecimal (2.5.1-r1)
(9/13) Installing ncurses-terminfo-base (6.2_p20210612-r0)
(10/13) Installing ncurses-libs (6.2_p20210612-r0)
(11/13) Installing readline (8.1.0-r0)
(12/13) Installing sqlite-libs (3.35.5-r0)
(13/13) Installing python3 (3.9.5-r1)
Executing busybox-1.33.1-r3.trigger
OK: 56 MiB in 27 packages
STEP 3: COMMIT mypython
--> 20332d09dbe
Successfully tagged localhost/mypython:latest
20332d09dbe5b483818a106bf5e72bceb478c2274686db476bb3097f0d8a0895

$ podman images
REPOSITORY                TAG           IMAGE ID      CREATED         SIZE
localhost/mypython        latest        20332d09dbe5  58 seconds ago  54.2 MB
docker.io/library/python  3-alpine3.14  d4d6be1b90ec  34 hours ago    48 MB
docker.io/library/alpine  3.14          021b3423115f  7 days ago      5.87 MB
```
有趣的是，發現自己用 alpine 當 base 裝 python3，最後 build 出來的 image 比直接從 dockerhub 上拉一樣版本的 Python image [python:3-alpine3.14](https://hub.docker.com/_/python) 還要肥大！當然，也不是所有的 case 都是這樣，但至少 Python image 是這樣。

## Add and execute a Python application in a container directly

```shell=
$ cat pyhello-dockerfile 
FROM docker.io/library/python:3-alpine3.14

COPY hello.py /usr/bin/

$ cat hello.py 
#!/usr/bin/env python3

print("Hello World!")

$ chmod +x hello.py

$ podman build -t python-hello -f pyhello-dockerfile 
STEP 1: FROM docker.io/library/python:3-alpine3.14
STEP 2: COPY hello.py /usr/bin/
STEP 3: COMMIT python-hello
--> 4d28af87192
Successfully tagged localhost/python-hello:latest
4d28af87192c0d1940d615c0b9a8b9a82590fd4181b8dd0e9ca5bb1a7ea43b7f

$ podman images
REPOSITORY                TAG           IMAGE ID      CREATED        SIZE
localhost/python-hello    latest        4d28af87192c  9 seconds ago  48 MB
docker.io/library/python  3-alpine3.14  d4d6be1b90ec  34 hours ago   48 MB
docker.io/library/alpine  3.14          021b3423115f  7 days ago     5.87 MB

$ podman run --name pyhello python-hello hello.py
Hello World!
```

## Have a bottle HTTP server in a container

Reference: [Running Bottle Apps in Docker Containers on macOS](https://www.fullstackpython.com/blog/first-steps-bottle-web-apps-docker-containers.html)

```shell=
$ ls app/
app.py  requirements.txt

$ cat app/requirements.txt 
bottle

$ cat app/app.py 
#!/usr/bin/env python3

from bottle import route, run

@route('/')
def hello_world():
    return "Hello, world! (From Full Stack Python)"

if __name__ == "__main__":
    run(host="0.0.0.0", port=8080, debug=True, reloader=True)

$ chmod +x app/app.py

$ cat bottle-dockerfile 
FROM docker.io/library/python:3-alpine3.14

WORKDIR /app

COPY app /app

RUN pip install --no-cache-dir -r requirements.txt

$ podman build -t mybottle -f bottle-dockerfile 
STEP 1: FROM docker.io/library/python:3-alpine3.14
STEP 2: WORKDIR /app
--> 4b04293273b
STEP 3: COPY app /app
--> fb59b39cb01
STEP 4: RUN pip install --no-cache-dir -r requirements.txt
Collecting bottle
  Downloading bottle-0.12.19-py3-none-any.whl (89 kB)
Installing collected packages: bottle
Successfully installed bottle-0.12.19
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
STEP 5: COMMIT mybottle
--> fe155ca85e1
Successfully tagged localhost/mybottle:latest
fe155ca85e13658653ab20d29f4c4b52558c39919c656ad37a4b372edd063d5b

$ podman images
REPOSITORY                TAG           IMAGE ID      CREATED             SIZE
localhost/mybottle        latest        fe155ca85e13  About a minute ago  55 MB
docker.io/library/python  3-alpine3.14  d4d6be1b90ec  34 hours ago        48 MB
docker.io/library/alpine  3.14          021b3423115f  7 days ago          5.87 MB
```
測試一下，要導 port 進 container：
```shell=
$ podman run -p 5000:8080 --name testbottle mybottle /app/app.py
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://0.0.0.0:8080/
Hit Ctrl-C to quit.

10.0.2.100 - - [14/Aug/2021 07:55:25] "GET / HTTP/1.1" 200 38
```
另一個 terminal
```shell=
$ curl localhost:5000
Hello, world! (From Full Stack Python)
```
也可把 container 起成像是 keep alive standalone daemon：
```shell=
$ podman run -d -p 5000:8080 --name testbottle mybottle /app/app.py
76f1bc5f3a31a2cef4d1d3a751d01cee04ab55abecb65c09a8a046600898b47e

$ podman ps -a
CONTAINER ID  IMAGE                      COMMAND      CREATED        STATUS            PORTS                   NAMES
76f1bc5f3a31  localhost/mybottle:latest  /app/app.py  8 seconds ago  Up 8 seconds ago  0.0.0.0:5000->8080/tcp  testbottle

# Stop the container
$ podman stop testbottle
testbottle

$ podman ps -a
CONTAINER ID  IMAGE                      COMMAND      CREATED        STATUS                       PORTS                   NAMES
76f1bc5f3a31  localhost/mybottle:latest  /app/app.py  3 minutes ago  Exited (137) 45 seconds ago  0.0.0.0:5000->8080/tcp  testbottle
```
可以看 standalone container 裡 process 的 log：
```shell=
$ podman logs mybottle
Bottle v0.12.19 server starting up (using WSGIRefServer())...
Listening on http://0.0.0.0:8080/
Hit Ctrl-C to quit.

10.0.2.100 - - [14/Aug/2021 14:43:14] "GET / HTTP/1.1" 200 38
```

也可以另外連進 standalone container，並取得、執行 shell
```shell=
$ podman exec -it mybottle sh
/app #
```

## Secrets

Container 裡的程式有時會需要一些敏感資料，如帳號、密碼。可以用 `-e --env` 設定 container 環境變數的方式，pass 一些參數。這可以搭配 Continuous Delivery (CD) 服務的 secret store。
```shell=
$ podman run --rm -it -e ENVTEST=iamtest alpine sh
/ # echo $ENVTEST
iamtest
```
另外，也可以用一些 Container 管裡工具的 secret store 功能，如 Podman、docker、[Kubernetes](https://kubernetes.io/) 和 cloud service provider 都有提供類似功能。

* [Podman secret command](http://docs.podman.io/en/latest/secret.html)
* [Podman - Secrets](https://blog.while-true-do.io/podman-secrets/)

以 Podman 為例，可以用 `--secret` pass secret object 進 container，Podman 會在 container 裡 mount 該 secret object 為一個 file `/run/secrets/<secret object's name>`：
```shell=
$ echo "iamsensitive" | podman secret create SecureData -

$ podman secret inspect SecureData
[
    {
        "ID": "ade70aaccf022dd93d039f1df",
        "CreatedAt": "2021-08-14T23:16:28.261537528+08:00",
        "UpdatedAt": "2021-08-14T23:16:28.261537528+08:00",
        "Spec": {
            "Name": "SecureData",
            "Driver": {
                "Name": "file",
                "Options": null
            }
        }
    }
]

$ podman run --rm --secret SecureData alpine cat /run/secrets/SecureData
iamsensitive
```

## Bind mount a volume into the container

利用 volume 的機制，把 host 上的 folder 掛進 container 裡：

```shell=
$ mkdir /tmp/data
$ date | tee /tmp/data/mydata
Sun Aug 15 11:55:01 AM CST 2021

$ podman run --rm -it -v /tmp/data:/home/dev alpine sh
/ # ls -l /home/dev/
total 4
-rw-r--r--    1 root     root            32 Aug 15 03:55 mydata
/ # cat /home/dev/mydata 
Sun Aug 15 11:55:01 AM CST 2021
/ #
```

## Reference

* MariaDB 在 dockerhub 上有很完整的[教學範例](https://hub.docker.com/_/mariadb) (以 docker 為例)