---
# System prepended metadata

title: '[雲端] Docker & Container / 範例集'
tags: [雲端, Docker, container, 雲端 / Docker & Container]

---

[雲端] Docker & Container / 範例集
===
###### tags: `雲端 / Docker & Container`
###### tags: `雲端`, `Docker`, `container`

<br>

[TOC]

<br>

# 新手上路
## Hello World
```bash
# 安裝 docker 套件，套件名稱決取於 OS 版本
$ sudo apt install docker.io

# 起一個 hello-world 的 docker; 
# 若 docker image 不存在，則會自動抓取
$ docker run hello-world
```

<br>

# 常用 web 映像檔測試
## ```nginx``` (port: 80)
- 啟動 container
    ```$ docker run --rm -d --net=host nginx```
    或是
    ```$ docker run --rm -d -p 8080:80 nginx``` (入口點 8080)
- 測試1
    http://10.78.26.241 或 http://10.78.26.241:80
- 測試2
    ```$ curl -X GET 127.0.0.1:80```
    - ```-X``` 要大寫
    - ```get``` 也要大寫
    - 預設參數是 ```-X GET```，可忽略
- 官網文件
    https://hub.docker.com/_/nginx
- GitHub
    https://github.com/nginxinc/docker-nginx

## ```hcwxd/blue-whale``` (port: 3000) (tag: latest, stable)
- 啟動 container
    ```$ docker run --rm -d --net=host hcwxd/blue-whale```
- 測試
    http://10.78.26.241:3000

## ```hcwxd/purple-whale``` (port: 3000) (tag: latest, stable)
- 啟動 container
    ```$ docker run --rm -d --net=host hcwxd/purple-whale```
- 測試
    http://10.78.26.241:3000

## ```gcr.io/kuar-demo/kuard-amd64:1``` (port: 8080)
- 啟動 container
    ```$ docker run --rm -d --net=host gcr.io/kuar-demo/kuard-amd64:1```
    - tags: 1, 2, blue, ...
- 測試
    http://10.78.26.241:8080/

## ```zxcvbnius/docker-demo``` (port: 3000) (tag: latest)
- 啟動 container
    ```$ docker run --rm -d --net=host zxcvbnius/docker-demo```
- 測試
    http://10.78.26.241:3000/
    ![](https://i.imgur.com/P5uvPBZ.png)

<br>

# 其他應用程式
## [Docker Registry](https://docs.docker.com/registry/)
```bash
# 下載並啟動 registry 的 docker 版本
# 2 為某一版
$ docker run  --restart=always -d -p 5000:5000 --name registry registry:2
```
- ### 步驟1： 測試 registry 是否有在執行中
    ```bash=
    # 測試 registry
    $ curl -X GET 127.0.0.1:5000/v2
        <a href="/v2/">Moved Permanently</a>.
    
    # 或是：
    # curl 預設使用 -X GET
    $ curl 127.0.0.1:5000/v2
        <a href="/v2/">Moved Permanently</a>.
    
    $ curl 127.0.0.1:5000/v2/_catalog
    {"repositories":[]}
    ```
- ### 步驟2：推送測試
    > 下載 nginx，並推送到本地端的 registry
    ```bash
    $ docker pull nginx
    $ docker tag nginx 127.0.0.1:5000/nginx_local
    $ docker images
    
    $ docker push 127.0.0.1:5000/nginx_local
        Using default tag: latest
        The push refers to repository [127.0.0.1:5000/nginx_local]
        4eaf0ea085df: Pushed 
        2c7498eef94a: Pushed 
        7d2b207c2679: Pushed 
        5c4e5adc71a8: Pushed 
        87c8a1d8f54f: Pushed 
        latest: digest: sha256:13e4551010728646aa7e1b1ac5313e04cf75d051fa441396832fcd6d600b5e71 size: 1362
    ```
- ### 步驟3：查看推送結果
    ```bash
    $ curl 127.0.0.1:5000
    
    $ curl 127.0.0.1:5000/v2
    <a href="/v2/">Moved Permanently</a>.
    
    $ curl 127.0.0.1:5000/v2/_catalog
        {"repositories":["nginx_local"]}
    
    $ curl 127.0.0.1:5000/v2/nginx_local/manifests/latest
        {
           "schemaVersion": 1,
           "name": "nginx_local",
           "tag": "latest",
           "architecture": "amd64",
           ...
        }
    ```
    - tag 名稱可以允許斜線
        `apache/airflow:2.0.1-python3.8-by-tj`
        
        `curl 127.0.0.1:5000/v2/apache/airflow/manifests/2.0.1-python3.8-by-tj`
- ### 步驟4：測試本地端 registry 上的 nginx
    ```bash
    $ docker images
    
    $ docker rmi 127.0.0.1:5000/nginx_local
        Untagged: 127.0.0.1:5000/nginx_local:latest
        Untagged: 127.0.0.1:5000/nginx_local@sha256:13e4551010728646aa7e1b1ac5313e04cf75d051fa441396832fcd6d600b5e71
    
    # 移除 local 端的 image 後
    # register 上的 image 還會在
    $ curl 127.0.0.1:5000/v2/_catalog
        {"repositories":["nginx_local"]}
    
    # 執行 nginx 前，先行測試連線
    $ curl 127.0.0.1
        curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused
    
    # 執行 nginx
    $ docker run --name nginx_local --rm --net=host -d 127.0.0.1:5000/nginx_local
    
    # 執行 nginx 後，測試連線
    $ curl 127.0.0.1
        ...
        <h1>Welcome to nginx!</h1>
        ...
    
    # 停止執行 nginx
    $ docker rm -f nginx_local
    
    # 再一次測試連線
    $ curl 127.0.0.1
        curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused
    ```
- Q&A
    - 使用 http 連 registry
        - ESC4000
            - 測試 psuh 錯誤
                ```bash
                $ docker pull nginx
                $ docker tag nginx 10.78.26.241:5000/nginx
                $ docker push 10.78.26.241:5000/nginx
                    The push refers to repository [10.78.26.241:5000/nginx]
                    Get https://10.78.26.241:5000/v2/: http: server gave HTTP response to HTTPS client
                ```
            - 將 10.78.26.241:5000 加入到 insecure-registries 白名單
                ```$ sudo cat /etc/docker/daemon.json```
                ```json
                {
                    ...
                    "insecure-registries": [
                        ...,
                        "10.78.26.241:5000"]
                }
                ```
                重啟 docker 服務
                ```bash
                $ systemctl restart docker
                    ==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
                    Authentication is required to restart 'docker.service'.
                    Multiple identities can be used for authentication:
                     ...
                    Choose identity to authenticate as (1-5): 3
                    Password: 
                    ==== AUTHENTICATION COMPLETE ===
                ```
            - 測試 http 連線結果
                ```bash
                # --rm 會導致 container 在 docker 服務重啟後消失
                $ docker run --rm -p 5000:5000 -d --name registry registry:2
                $ docker ps -a
                
                $ docker push 10.78.26.241:5000/nginx
                The push refers to repository [10.78.26.241:5000/nginx]
                4eaf0ea085df: Pushed 
                2c7498eef94a: Pushed 
                7d2b207c2679: Pushed 
                5c4e5adc71a8: Pushed 
                87c8a1d8f54f: Pushed 
                latest: digest: sha256:13e4551010728646aa7e1b1ac5313e04cf75d051fa441396832fcd6d600b5e71 size: 1362
                ```
        - [Day7：建立 private 的 Docker Registry](https://ithelp.ithome.com.tw/articles/10191213)
        - [[官網] Deploy a plain HTTP registry](https://docs.docker.com/registry/insecure/#deploy-a-plain-http-registry)
            ```/etc/docker/daemon.json```
            ```json
            {
              "insecure-registries" : ["myregistrydomain.com:5000"]
            }
            ```
    - 若要使用 https 連 private registry
        - [[官網] Use self-signed certificates](https://docs.docker.com/registry/insecure/#use-self-signed-certificates)
            ```bash
            $ mkdir -p certs

            $ openssl req \
              -newkey rsa:4096 -nodes -sha256 -keyout certs/domain.key \
              -x509 -days 365 -out certs/domain.crt
            ```
            - [Get a certificate](https://docs.docker.com/registry/deploying/#get-a-certificate)
    - 若是遇到 `Get https://10.78.26.172:31352/v2/: x509: certificate signed by unknown authority`，解決方法如下：
        
        ```bash=
        $ sudo vi /etc/docker/daemon.json
        加入insecure-registries
        {  
           "insecure-registries":["your_host_ip"]
        }
        $ sudo systemctl restart docker
        ```
        
        :::warning
        :bulb: Upload images to 10.78.26.172
        ```bash
        $ docker push 10.78.26.172:31352/4d5fb274-059a-448a-82da-5ca0a0c1468b-pub/ml-sklearn-jsd8ri:v1
        The push refers to repository [10.78.26.172:31352/4d5fb274-059a-448a-82da-5ca0a0c1468b-pub/ml-sklearn-jsd8ri]
        Get https://10.78.26.172:31352/v2/: x509: certificate signed by unknown authority
        
        # === solution ===
        $ sudo nano /etc/docker/daemon.json
        {
            "insecure-registries": [
                "localhost:5000",
                "127.0.0.1:5000",
                "10.78.26.172:31352"
            ]
        }

        $ sudo service docker restart
        $ docker login 10.78.26.172:31352
        Username: tj_tsai@asus.com
        Password: O***************
        Login Succeeded
        
        $ docker push 10.78.26.172:31352/4d5fb274-059a-448a-82da-5ca0a0c1468b-pub/ml-sklearn-jsd8ri:v1
        ```
        :::
        
<br>

## Kuard
### image
```gcr.io/kuar-demo/kuard-amd64:1``` (port: 8080)
- 啟動 container
    ```$ docker run --rm -d --net=host gcr.io/kuar-demo/kuard-amd64:1```
- 測試
    http://10.78.26.241:8080/

### 功能 / Memq server (memory-queue server)
[![](https://i.imgur.com/JPyYYSB.png)](https://i.imgur.com/JPyYYSB.png)
- ### 欄位說明
    ![](https://i.imgur.com/zUXKkww.png)
    - **狀態**
        - Name：佇列名稱
        - Depth：當前佇列深度(當前佇列長度)
    - **動作**
        - Enqueued：入佇列
        - Dequeued：出佇列
        - Drained (drain + ed)：流乾，耗盡、清空佇列
            - drain 洩流、排水
    - **數量上關係**
        enqueued = depth + dequeued + drained
- ### 獲取所有佇列的統計資訊
    > Get stats on all queues
    
    ```$ curl -X GET localhost:8080/memq/server/stats |     jq```
    或是
    ```$ curl localhost:8080/memq/server/stats | jq```
    (```-X GET``` 為預設選項)
    <br>
    空佇列的情況下：
    ![](https://i.imgur.com/7eNKs7x.png)
    ```yaml=
    {
      "kind": "stats",
      "queues": []
    }
    ```
    
    <br>
    
    建立佇列後的情況下：
    ![](https://i.imgur.com/yUaTiTG.png)
    ```yaml=
    {
      "kind": "stats",
      "queues": [
        {
          "name": "my-queue",
          "depth": 0,
          "enqueued": 0,
          "dequeued": 0,
          "drained": 0
        }
      ]
    }
    ```
    
    <br>
    
    佇列非為空的情況下：
    ![](https://i.imgur.com/dtpvVCe.png)
    ```yaml
    {
      "kind": "stats",
      "queues": [
        {
          "name": "my-queue",
          "depth": 1,
          "enqueued": 1,
          "dequeued": 0,
          "drained": 0
        }
      ]
    }
    ```
- ### 建立一個佇列
    > Create a queue
    ```
     $ curl -X PUT localhost:8080/memq/server/queues/my-queue
    ```
    ![](https://i.imgur.com/dyFBwD5.png)
- ### 刪除一個佇列
    > Delete a queue
    ```
     curl -X DELETE localhost:8080/memq/server/queues/my-queue
    ```
- ### 丟棄佇列中的所有項目
    > Discard all items in queue
    ```
    $ curl -X POST localhost:8080/memq/server/queues/my-queue/drain
    ```
    執行前狀態：
    ![](https://i.imgur.com/h0tVgRr.png)
    執行後狀態：
    ![](https://i.imgur.com/acI8bQM.png)
- ### 入佇列：將項目添加到佇列
    > Add item to queue. Body is plain text. Response is message object.
    ```
    $ curl -X POST localhost:8080/memq/server/queues/my-queue/enqueue | jq
    ```
    執行前狀態：
    ![](https://i.imgur.com/dyFBwD5.png)
    執行後狀態：
    ![](https://i.imgur.com/eNUV02i.png)
    ![](https://i.imgur.com/7pBUTwV.png)
    回傳值：
    ```yaml=
    {
      "kind": "message",
      "id": "63fbf26cca68e7ab4ed1fa6de70b3bff",
      "body": "",
      "creationTimestamp": "2021-01-05T03:26:46.892147849Z"
    }
    ```
- ### 出佇列：將項目從佇列中移出，並回傳
    > Grab an item off the queue and return it. Returns a 204 "No Content" if queue is empty.
    執行前狀態：
    ![](https://i.imgur.com/7pBUTwV.png)
    執行後狀態：
    ![](https://i.imgur.com/h0tVgRr.png)
    回傳值：
    ```yaml=
    $ curl -X POST localhost:8080/memq/server/queues/my-queue/dequeue | jq
    {
      "kind": "message",
      "id": "63fbf26cca68e7ab4ed1fa6de70b3bff",
      "body": "",
      "creationTimestamp": "2021-01-05T03:26:46.892147849Z"
    }
    ```
    

<br>

## Ubuntu 18.04
```bash
$ docker run --name=ubuntu-1804 \
    --hostname=tj-desktop-d \
    --restart=always \
    -it \
    ubuntu:18.04 \
    /bin/bash &
```
- 額外的 package
    ```bash
    apt install curl
    apt install file     
    apt install systemd  # for systemctl
    ```

<br>

## Python
### 官方映像檔
https://hub.docker.com/_/python

### 啟動
```bash=
$ docker run --rm -it python:3
```


<br>

# Dockerfile
## 父程序 & 子程序
- ### `mybashrc`
    ```bash=
    # mybashrc
    export NAME='tj_tsai'
    ```
    - `$ sh mybashrc && echo $NAME`
        不能 work
    - `$ bash mybashrc && echo $NAME`
        不能 work
    - `$ chmod +x mybashrc && ./mybashrc && echo $NAME`
        不能 work
    - `$ . mybashrc && echo $NAME`
        ==可以 work== :+1: :100: 
    - `export` 指令
        是將變數傳遞給**子程序**、**子子程序**所使用，並不是將變數 export 到父程序
- ### 在 Dockerfile 中的正確作法
    ```dockerfile=
    FROM continuumio/miniconda3

    SHELL ["/bin/bash", "-c"]

    COPY mybashrc .
    RUN . mybashrc && echo "${NAME}:" ${NAME}
    ```

<br>

## `source ~/.bashrc`
conda 環境切換：
```bash
$ conda init bash
$ source ~/.bashrc
$ conda activate myenv
```

:::warning
:warning: **在 Dockerfile 裡，執行 `source ~/.bashrc` 可能無法如預期**

有些 image 的 `.bashrc` 會檢查是否為互動模式？
![](https://i.imgur.com/OptaGh8.png)
因為`$PS1` 為空值，在前面就會跳出 `.bashrc`
導致 `.bashrc` 後面的 conda init 的 script 沒有被執行到

- [Why does bashrc check whether the current shell is interactive?](https://unix.stackexchange.com/questions/257571)
:::

- ### solution1
    `source activate myenv`
    - [Python Anaconda: should I use `conda activate` or `source activate` in linux](https://stackoverflow.com/questions/49600611)
- ### solution2
    `. activate myenv`
    - [Activating environment in dockerfile - related to #81 #89](https://github.com/ContinuumIO/docker-images/issues/89#issuecomment-440574150)
- ### solution3 (在 `~/.bashrc` 有此指令)
    自行呼叫
    `. /root/miniconda3/etc/profile.d/conda.sh`
- ### solution4 (在 `~/.bashrc` 有此指令)
    自行呼叫
    `eval "$(command conda 'shell.bash' 'hook' 2> /dev/null)"`
    - [Python - Activate conda env through shell script](https://stackoverflow.com/a/58081608/4359712)
        ![](https://i.imgur.com/nJvskcl.png)
    
<br>

## Nvidia cuda base images
- [`nvidia/cuda`](https://hub.docker.com/r/nvidia/cuda/tags?page=1&name=11.3)
    - [`nvidia/cuda:11.2.0-cudnn8-devel-ubuntu18.04`](https://hub.docker.com/layers/nvidia/cuda/11.2.0-cudnn8-devel-ubuntu18.04/images/sha256-25afab0ac85a537a526528c7e9dcb0a56c60b6e9f7a99c0e1fdc1dd0e543671b?context=explore)

<br>

## Anaconda, Miniconda

### conda 環境切換問題，避免 conda init 問題的範例說明
底下兩種方法皆可，注意 source 差異性
- **方法1**：使用 `source ~/.bashrc` (註：在其他 image 不能 work)
    ```dockerfile=
    FROM continuumio/miniconda3

    SHELL ["/bin/bash", "-c"]

    # ~/.bashrc = /root/.bashrc
    RUN source ~/.bashrc \
        && conda create -n myenv -c conda-forge \
        && conda env list \
        && conda activate myenv \
        && conda env list \
        && echo 'DONE' > DONE
    ```
    :::warning
    :warning: **方法1：在其他 image 不能 work**
    `source ~/.bashrc` 只是去執行 `. /opt/conda/etc/profile.d/conda.sh`
    一般 image，執行 init conda 效果就如同上述，且 conda.sh 是一樣的
    在 `continuumio/miniconda3` 可以 work
    在一般 image 不 work，依舊會發生：
    ```
    CommandNotFoundError: Your shell has not been properly configured to use 'conda activate'.
    To initialize your shell, run

        $ conda init <SHELL_NAME>

    Currently supported shells are:
      - bash
      - fish
      - tcsh
      - xonsh
      - zsh
      - powershell

    See 'conda init --help' for more information and options.

    IMPORTANT: You may need to close and restart your shell after running 'conda init'.
    ```
    :::
    - `~/.bashrc` (`/root/.bashrc`)
        ![](https://i.imgur.com/P6KrlGB.png) 
    - diff(一般image, `continuumio/miniconda3`)
        [![](https://i.imgur.com/QlS8Rdt.png)](https://i.imgur.com/QlS8Rdt.png)
- **方法2**：使用 `source activate myenv`
    ```dockerfile=
    FROM continuumio/miniconda3

    SHELL ["/bin/bash", "-c"]

    RUN conda create -n myenv -c conda-forge \
        && conda env list \
        && source activate myenv \
        && conda env list \
        && echo 'DONE' > DONE
    ```
    :::warning
    :warning: **限制1：conda activate 應該是 in-memory，所以每次開新的 shell 又回到 base**
    ```dockerfile=
    FROM continuumio/miniconda3

    SHELL ["/bin/bash", "-c"]

    RUN conda create -n myenv -c conda-forge \
        && conda env list \
        && echo 'DONE' > DONE

    RUN source activate myenv \
        && conda env list \
        && echo 'DONE2' > DONE

    RUN conda env list \
        && echo 'DONE3' > DONE
    ```
    ![](https://i.imgur.com/7E5U1Ve.png)
    :::
    
    :::warning
    :warning: **限制2：同樣問題，每次的 RUN，shell 又回到原始狀態，沒套用 ~/.bashrc**
    ```dockerfile=
    FROM continuumio/miniconda3

    SHELL ["/bin/bash", "-c"]

    RUN source ~/.bashrc \
        && conda create -n myenv -c conda-forge \
        && conda env list \
        && echo 'DONE' > DONE

    # [failed] CommandNotFoundError: Your shell has not been properly configured to use 'conda activate'.
    RUN conda activate myenv \
        && conda env list \
        && echo 'DONE2' > DONE

    RUN conda env list \
        && echo 'DONE3' > DONE
    ```
    ![](https://i.imgur.com/wfPoLKe.png)
    :::
    - [Activate and switch Anaconda environment in Dockerfile during build](https://stackoverflow.com/a/56512187/4359712)
        - each RUN command starts a new shell
        - conda activate applies only to the current shell.
        - ==**Keep in mind:**==
            > at the end of that RUN command, the shell will be gone. So if you want to do something else to that conda environment afterwards, you've got to run conda activate again

- **最終版：Dockerfile**
    ```dockerfile=
    # Dockerfile
    FROM continuumio/miniconda3

    SHELL ["/bin/bash", "-c"]

    # create a new conda environment
    RUN conda create -n myenv -y python=3.5 \
        && conda env list \
        && source ~/.bashrc \
        && conda activate myenv \
        && conda env list 

    RUN echo "conda activate myenv" >> ~/.bashrc

    # test:
    # $ docker build -t myconda-py3.5 . --no-cache
    # $ docker run --rm -it myconda-py3.5
    ```
    - `$ docker build -t myconda-py3.5 . --no-cache`
        ![](https://i.imgur.com/6nZ1qj2.png)
    - `$ docker run --rm -it myconda-py3.5`
        ![](https://i.imgur.com/uwRoCLm.png)

### [Activate and switch Anaconda environment in Dockerfile during build](https://stackoverflow.com/questions/56510575/)
### The simpler sample for Dockerfile
You can focus on switching the conda environment.
- **Step 1**: Prepare the `Doockerfile`.
    ```dockerfile=
    FROM continuumio/miniconda3

    SHELL ["/bin/bash", "-c"]

    # create a new conda environment
    RUN conda create -n myenv -y python=3.5 \
        && conda env list \
        && source ~/.bashrc \
        && conda activate myenv \
        && conda env list 

    RUN echo "conda activate myenv" >> ~/.bashrc

    # test:
    # $ docker build -t myconda-py3.5 . --no-cache
    # $ docker run --rm -it myconda-py3.5
    ```
- **Step 2**: Build the dockerfile.
    run:
    ```
    docker build -t myconda-py3.5 . --no-cache
    ```
    You will see that the conda environment is switched to `myenv` in the building log.
    ![](https://i.imgur.com/6nZ1qj2.png)
    Note:
    - Each `RUN` command will start a new shell.
    <br>
- **Step 3**: start a container:
    run:
    ```
    docker run --rm -it myconda-py3.5
    ```
    You will see that the conda environment is automatically switched to `myenv`.
        ![](https://i.imgur.com/1uGXUwE.png)


### Q & A
- ### [How to install packages with miniconda in Dockerfile?](https://stackoverflow.com/questions/58269375)
    ```Dockefile
    FROM ubuntu:18.04
    ENV PATH="/root/miniconda3/bin:${PATH}"
    ARG PATH="/root/miniconda3/bin:${PATH}"
    RUN apt-get update

    RUN apt-get install -y wget && rm -rf /var/lib/apt/lists/*

    RUN wget \
        https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh \
        && mkdir /root/.conda \
        && bash Miniconda3-latest-Linux-x86_64.sh -b \
        && rm -f Miniconda3-latest-Linux-x86_64.sh 
    RUN conda --version
    ```
    :::warning
    :warning: **要使用雙引號，不能使用單引號**
    如果使用 `ENV PATH='/root/miniconda3/bin:${PATH}'`
    容器在啟動時，會有 error log:
    ```
    bash: groups: command not found
    bash: dircolors: command not found
    ```
    
    正確要使用：
    `ENV PATH="/root/miniconda3/bin:${PATH}"`
    :::
    ![](https://i.imgur.com/FOCOvmR.png)

- **source 的問題**
    1. **It works!** (but: env 仍然是 base)
        ```Dockerfile
        SHELL ["/bin/bash", "-c"]
        RUN source activate parabricks
        ```
        - 參考資料
            - [Using the RUN instruction in a Dockerfile with 'source' does not work](https://stackoverflow.com/questions/20635472)
                ![](https://i.imgur.com/PzvcQfM.png)
                - [[docker docs] SHELL](https://docs.docker.com/engine/reference/builder/#shell)
            - [Conda activate not working?](https://stackoverflow.com/questions/47246350)
                - `conda activate <envname>`
                - `source activate <envname>`
                - `activate <envname>`
        <br>
    2. **It works!** (but: env 仍然是 base)
        > https://stackoverflow.com/a/58871627/4359712
        ```Dockerfile
        SHELL ["/bin/bash", "-c"]
        RUN source ~/.bashrc \
            && conda env create -f environment.yml  \
            && conda activate myenv 
        ```
        - `environment.yml`
            ```yml
            name: myenv
            channels:
              - conda-forge
            dependencies:
              - python=3.8
              - flask
            ```
            - [Activating a Conda environment in your Dockerfile](https://pythonspeed.com/articles/activate-conda-dockerfile/)
        <br>
    3. 先 init, 再 create 也不 work
        - [Your shell has not been properly configured to use 'conda activate' on dockerfile](https://stackoverflow.com/a/60855167/4359712)
        
- ### 切換 shell
    ```Dockerfile
    RUN echo Case0: $0
    RUN ps -p$$
    
    SHELL ["/bin/bash", "-c"]
    RUN echo Case1: $0
    RUN ps -p$$
    
    SHELL ["/bin/sh", "-c"]
    RUN echo Case2: $0
    RUN ps -p$$
    
    SHELL ["/bin/dash", "-c"]
    RUN echo Case3: $0
    RUN ps -p$$
    ```
    執行結果：
    ```
    Case0: /bin/sh
        PID TTY          TIME CMD
          1 ?        00:00:00 sh

    Case1: /bin/bash
        PID TTY          TIME CMD
          1 ?        00:00:00 ps

    Case2: /bin/sh
        PID TTY          TIME CMD
          1 ?        00:00:00 sh

    Case3: /bin/dash
        PID TTY          TIME CMD
          1 ?        00:00:00 dash
    ```
- ### [Activating a Conda environment in your Dockerfile](https://pythonspeed.com/articles/activate-conda-dockerfile/) (不 work)
    - **`environment.yml`**
        ```yml
        name: myenv
        channels:
          - conda-forge
        dependencies:
          - python=3.8
          - flask
        ```
    - **`Dockerfile`**
        ```Dockerfile
        FROM continuumio/miniconda3

        WORKDIR /app

        # Create the environment:
        COPY environment.yml .
        RUN conda env create -f environment.yml

        # Activate the environment, and make sure it's activated:
        RUN conda activate myenv
        RUN echo "Make sure flask is installed:"
        RUN python -c "import flask"

        # The code to run when container is started:
        COPY run.py .
        ENTRYPOINT ["python", "run.py"]
        ```
    - 一樣不 work,
        `$ docker build -t myconda-py8 .`
        ```
        Step 5/9 : RUN conda activate myenv
         ---> Running in a7ef77191fa4

        CommandNotFoundError: Your shell has not been properly configured to use 'conda activate'.
        To initialize your shell, run

            $ conda init <SHELL_NAME>

        Currently supported shells are:
          - bash
          - fish
          - tcsh
          - xonsh
          - zsh
          - powershell

        See 'conda init --help' for more information and options.

        IMPORTANT: You may need to close and restart your shell after running 'conda init'.


        The command '/bin/sh -c conda activate myenv' returned a non-zero code: 1
        ```
        ![](https://i.imgur.com/9Wh1wcI.png)

<br>

## Timezone
### 時區檢查
```bash
$ timedatectl
Failed to create bus connection: No such file or directory
```

```bash
$ docker run --rm -it -v /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro xxx_image
```
- [timedatectl: Failed to create bus connection: No such file or directory](https://askubuntu.com/questions/1190804)
    > This is caused by the container not having access to the system_bus_socket on the host. You need to run your container with:

### Dockerfile
```dockerfile=
# [Timezone]
# Avoiding user interaction with tzdata
# when installing the parabricks.deb package.
# --------------------------------------------
# log:
#
#   Configuring tzdata
#   ------------------
#   Please select the geographic area in which you live. Subsequent configuration
#   questions will narrow this down by presenting a list of cities, representing
#   the time zones in which they are located.
# 
#     1. Africa        6. Asia            11. System V timezones
#     2. America       7. Atlantic Ocean  12. US
#     3. Antarctica    8. Europe          13. None of the above
#     4. Australia     9. Indian Ocean
#     5. Arctic Ocean  10. Pacific Ocean
# 
# solutions:
#  - https://askubuntu.com/questions/909277/
#    ENV DEBIAN_FRONTEND=noninteractive
#  - https://cynthiachuang.github.io/Change-Timezone-in-Dockerfile/
#  - https://ubuntuqa.com/zh-tw/article/7684.html
#
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && apt install tzdata
RUN TZ=Asia/Taipei \
	&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \
	&& echo $TZ > /etc/timezone \
	&& dpkg-reconfigure -f noninteractive tzdata
```
![](https://i.imgur.com/256JWHu.png)
- `TZ=Asia/Taipei` 設定環境變數
- `ln TARGET LINK_NAME`
    - `-s, --symbolic`
        make symbolic links instead of hard links
    - `-f, --force`
        remove existing destination files
    - `-n, --no-dereference`
        treat LINK_NAME as a normal file if
- **參考資料**
    - [Avoiding user interaction with tzdata when installing certbot in a docker container](https://askubuntu.com/questions/909277/)
        ![](https://i.imgur.com/n1IyQ1A.png)
        ```dockerfile
        ENV DEBIAN_FRONTEND=noninteractive
        ```
- **失敗情況1**
    ```bash
    TZ=Asia/Taipei
	echo $TZ > /etc/timezone
    apt install tzdata
    ```
- **成功情況1**
    ```bash
    TZ=Asia/Taipei \
    ln -snf /usr/share/zoneinfo/$TZ /etc/localtime 
    apt install tzdata
    ```
- **成功情況2**
    ```bash
    export DEBIAN_FRONTEND=noninteractive
    apt install tzdata
    ```
    - 沒有加 export 會失敗

<br>

<hr>

<br>

# docker-engine 指令

## 重啟 docker daemon
- [Control Docker with systemd](https://docs.docker.com/config/daemon/systemd/)
    - systemctl:
        ```
        $ sudo systemctl start docker
        ```
        [![](https://i.imgur.com/OocVOZw.png)](https://i.imgur.com/OocVOZw.png)
    - service:
        ```
        $ sudo service docker start
        ```
    
<br>

<hr>

<br>


# docker 指令

## docker builder 

### docker builder prune
```bash
$ docker builder prune -h
Flag shorthand -h has been deprecated, please use --help

Usage:  docker builder prune

Remove build cache

Options:
  -a, --all                  Remove all unused build cache, not just dangling ones
      --filter filter        Provide filter values (e.g. 'until=24h')
  -f, --force                Do not prompt for confirmation
      --keep-storage bytes   Amount of disk space to keep for cache
```
```bash
$ docker builder prune
WARNING! This will remove all dangling build cache. Are you sure you want to continue? [y/N] y

Deleted build cache objects:
os3dv0og4js3aykibv6btazw5
...
zujxetmehzyu0gngc5l0e5x00

Total reclaimed space: 3.873GB
```


<br>

## docker clean (docker container prune, docker system prune)
> `Exited (1) 6 days ago`

```bash
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] N


$ 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] N
```
- [How to remove old Docker containers](https://stackoverflow.com/questions/17236796/)
- [How To Remove Docker Images, Containers, and Volumes](https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes) :+1: :+1: :+1: :100: 

<br>

## docker history 
### 操作範例
Dockerfile
```dockerfile=
FROM ubuntu:18.04
CMD [ "--help" ]
RUN pwd
```

`$ docker build -t ubuntu-test:18.04 .`

`$ docker history ubuntu-test:18.04`
```
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
b223b36187ba        2 seconds ago       /bin/sh -c pwd                                  0B                  
96c17ddc29e2        3 seconds ago       /bin/sh -c #(nop)  CMD ["--help"]               0B                  
2c047404e52d        17 months ago       /bin/sh -c #(nop)  CMD ["/bin/bash"]            0B                  
<missing>           17 months ago       /bin/sh -c mkdir -p /run/systemd && echo 'do…   7B                  
<missing>           17 months ago       /bin/sh -c [ -z "$(apt-get indextargets)" ]     0B                  
<missing>           17 months ago       /bin/sh -c set -xe   && echo '#!/bin/sh' > /…   745B                
<missing>           17 months ago       /bin/sh -c #(nop) ADD file:6ef542de9959c3061…   63.3MB
```
- 後面添加的指令，在最上層
- 參數 `--no-trunc` 可以顯示完整的 `CREATED BY` 資訊

### 實際應用：找出 base image
- [Is there a command to find out the base image of a Docker image?](https://stackoverflow.com/questions/59892922)
    ```
    $ docker history t:1
    $ docker inspect 16a54299f91e
        [
            {
                "Id": "sha256:16a54299f91ef62cf19d7329645365fff3b7a3bff4dfcd8d62f46d0c9845b9c6",
                "RepoTags": [
                    "python:3.7-alpine"   ---> Base image used in FROM instruction. 
    ```
- [How to find out the base image for a docker image](https://stackoverflow.com/questions/58018422)
    ```
    docker image history deno
    ```
    - For Linux
        ```
        docker image ls | grep a24bb4013296
        ```

<br>

## docker image

## docker image prune
```bash
$ docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N] y
Total reclaimed space: 0B
```

<br>

## docker info
```
$ docker info
Containers: 1
 Running: 1
 Paused: 0
 Stopped: 0
Images: 7
Server Version: 18.09.7
...
(more)
```

<br>

## docker inspect (檢視容器組態config)
```bash
$ docker ps -a
$ docker inspect 26bdbf9a7671 --format="{{json . }}" | jq

# or
$ sudo cat /var/lib/docker/containers/26bdbf9a76710b18adf653663bd3e90d42a22c19aaacfac9931cd2e410ebc5d4/config.v2.json | jq
```
- port
    ```json=
    "PortBindings": {
        "9999/tcp": [
            {
                "HostIp": "0.0.0.0",
                "HostPort": "9999"
            }
        ]
    },

    "Ports": {
        "22/tcp": null,
        "8888/tcp": null,
        "9999/tcp": [
            {
                "HostIp": "0.0.0.0",
                "HostPort": "9999"
            }
        ]
    },
    ```
    - [Connection refused when connecting to the exposed port on docker container](https://stackoverflow.com/questions/58454601)

<br>

## docker login
- `$ docker login index.docker.io`
    ```json
    {
            "auths": {
                    "index.docker.io": {
                            "auth": "b2Npc2Nsb3VkOk9jIXMjMjAyMSM="
                    }
            }
    }
    ```
- `$ docker login`
    ```json
    {
            "auths": {
                    "https://index.docker.io/v1/": {
                            "auth": "b2Npc2Nsb3VkOk9jIXMjMjAyMSM="
                    }
            }
    }
    ```
- 自動化
    ```
    echo '*******' | docker login --username ociscloud --password-stdin
    ```
    - 參考資料
        - [Provide a password using STDIN](https://docs.docker.com/engine/reference/commandline/login/#provide-a-password-using-stdin)


## docker port
```
$ docker port minikube
22/tcp -> 127.0.0.1:49162
2376/tcp -> 127.0.0.1:49161
5000/tcp -> 127.0.0.1:49160
5000/tcp -> 0.0.0.0:5000
8443/tcp -> 127.0.0.1:49159
```

<br>

## docker ps 
- 列出所有的 container
    ```bash
    $ docker ps -a
    ```
- 列出已登出的 container
    ```bash
    $ docker ps -a -f status=exited
    ```
- 刪除已登出的 container
    ```bash
    $ docker rm $(docker ps -a -f status=exited -q)
    ```
- 參考資料
    - [Removing Containers](https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes)

<br>

## docker pull
- ### 對所有的 image 進行更新
    > `$ docker images | awk '{print $1":"$2}' | xargs sh -c 'for item in $@; do echo "-------------------------"; echo "docker pull $item"; docker pull $item; done'`

    - `docker images | awk '{print $1":"$2}'`
        ```bash
        $ docker images | awk '{print $1":"$2}'
        REPOSITORY:TAG
        vasapp-test-img:latest
        dsarchive/dsa_common:latest
        lscr.io/linuxserver/code-server:latest
        <none>:<none>
        dsarchive/histomicstk:latest
        python:3
        ```
    - 執行結果：
        ```
        -------------------------
        docker pull vasapp-test-img:latest
        Error response from daemon: pull access denied for vasapp-test-img, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
        -------------------------
        docker pull dsarchive/dsa_common:latest
        latest: Pulling from dsarchive/dsa_common
        Digest: sha256:2e71115ae4365a28019915c3816fe72a4a2e71cfc7efaf5ca04bd22e963699a8
        Status: Image is up to date for dsarchive/dsa_common:latest
        docker.io/dsarchive/dsa_common:latest
        -------------------------
        docker pull lscr.io/linuxserver/code-server:latest
        latest: Pulling from linuxserver/code-server
        Digest: sha256:a0540aabaad6ccf371102f79a80a6961c79ee33fb0f05ecf78f002e809efd872
        Status: Image is up to date for lscr.io/linuxserver/code-server:latest
        lscr.io/linuxserver/code-server:latest
        -------------------------
        docker pull <none>:<none>
        invalid reference format
        -------------------------
        ```

<br>

## docker run
### 讓容器「常駐」
- ### 方法一：用 `-d` 背景執行

    ```bash
    docker run -dit --name=ubuntu-test ubuntu:24.04
    ```
    - `-d` = detached（背景執行）
    - `-i` = 保留 STDIN
    - `-t` = 分配終端

    這樣容器會跑起來，但因為沒有指令，所以它會結束。解法就是給一個「不會結束的指令」，例如：

    ```bash
    docker run -dit --name=ubuntu-test ubuntu:24.04 tail -f /dev/null
    ```

    > `tail -f /dev/null` 會讓容器永遠卡在那邊，相當於一個「守護行程」。

    然後再進去容器：

    ```bash
    docker exec -it ubuntu-test bash
    ```


### `--gpus`
### `--runtime=nvidia`
### [`--gpus` | `--runtime=nvidia`](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/user-guide.html#gpu-enumeration)
- 預設值
    > all GPUs will be accessible, this is the default value in base CUDA container images.


    ```
    --gpus=all
    ```
- 設定 gpu 數量為 2 張
    ```
    --gpus 2
    ```
- 指定 gpu-1 和 gpu-2
    ```
    --gpus '"device=1,2"'
    ```
- 傳統(舊式) vs 新式
    - [RAPIDS - Open GPU Data Science](https://catalog.ngc.nvidia.com/orgs/nvidia/teams/rapidsai/containers/rapidsai)
        [![](https://i.imgur.com/WW3UxPM.png)](https://i.imgur.com/WW3UxPM.png)
        - Legacy - Docker CE v18 and nvidia-docker2
            `$ docker run --runtime=nvidia ...`
        - Preferred - Docker CE v19+ and nvidia-container-toolkit
            `$ docker run --gpus all ...`

<br>

## docker save & docker load
- 參考資料
    - [開始使用Docker：升空](https://chunyeung.medium.com/%E9%96%8B%E5%A7%8B%E4%BD%BF%E7%94%A8docker-%E5%8D%87%E7%A9%BA-be689ca992ab)
        ```
        docker save verse_gapminder > verse_gapminder.tar
        docker load --input verse_gapminder.tar
        ```

<br>

## docker system 
### docker system df
> Show docker disk usage

### docker system events
> Get real time events from the server

### docker system info
> Display system-wide information

### docker system prune
> Remove unused data

Run 'docker system COMMAND --help' for more information on a command.

<br>

## docker tag
- [重新命名映像檔(image)](https://www.edureka.co/community/69685/how-to-rename-docker-images-without-rebuilding-it)
    ```
    docker tag OldName:tag NewName:tag
    ```

<br>


## docker version
```bash
$ docker version
Client:
 Version:           18.09.7
 API version:       1.39
 Go version:        go1.10.4
 Git commit:        2d0083d
 Built:             Fri Aug 16 14:19:38 2019
 OS/Arch:           linux/amd64
 Experimental:      false

Server:
 Engine:
  Version:          18.09.7
  API version:      1.39 (minimum version 1.12)
  Go version:       go1.10.4
  Git commit:       2d0083d
  Built:            Thu Aug 15 15:12:41 2019
  OS/Arch:          linux/amd64
  Experimental:     false
```

<br>

<hr>

<br>

# docker build 指令
## COPY v.s. ADD
- ADD 把檔案放到 image, COPY 把檔案放到 container
- ADD 支援 URL 路徑
- ADD 可自動解開 tar 的壓縮功能
- [What is the difference between the 'COPY' and 'ADD' commands in a Dockerfile?](https://stackoverflow.com/questions/24958140/)
    > Note that the **Best practices** for writing Dockerfiles suggests using COPY where the magic of ADD is not required. 
- [[官方] ADD or COPY](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#add-or-copy)

:::warning
:bulb: **COPY 注意事項**
- `COPY XXX ~/` 會失敗，COPY 指令不認得 `~`
- `COPY XXX /root/` 會成功
:::

<br>

## ARG v.s. ENV
- [測試紀錄](https://github.com/tsungjung411/docker-study/blob/master/dockerfiles/test/arg_and_env.md)
- [Set today's date as environment variable](https://stackoverflow.com/questions/65492739/)
    ```dockerfile=
    FROM base

    # to be able to use in Dockerfile
    ARG now

    # to store the value as environment variable in the image
    ENV build_date=$now
    ```
    ```shell=
    # pass value explicitly
    docker build --build-arg now="$(date +%Y%m%d)" .

    # pass value from environment
    export now="$(date +%Y%m%d)"
    docker build --build-arg now .
    ```
- [docker build with --build-arg with multiple arguments](https://stackoverflow.com/questions/42297387/)
    ```dockerfile=
    docker build \
    -t essearch/ess-elasticsearch:1.7.6 \
    --build-arg number_of_shards=5 \
    --build-arg number_of_replicas=2 \
    --no-cache .
    ```

<br>

## CMD v.s. ENTRYPOINT
- ### 主原則：
    - ```CMD``` 和 ```ENTRYPOINT``` 可以互為通用
- ### 次原則：
    - 當 ```CMD``` 和 ```ENTRYPOINT``` 並存時
        - ```ENTRYPOINT``` 就是主程式
        - ```CMD``` 會變成參數
        - 執行的指令會變成 ```ENTRYPOINT``` + ```' '``` + ```CMD```
- ### 次次原則
    - 單獨使用 CMD，可被 docker run 後面的參數取代
        ```docker run image 參數1 參數2 ...```
        入口點程式：```參數1(作為執行檔，取代CMD) 參數2(作為執行檔的參數)```
        <br>
    - 單獨使用 ENTRYPOINT，docker run 後面的參數是作為 ENTRYPOINT 的參數
        ```docker run image 參數1 參數2 ...``` 
        入口點程式：```ENTRYPOINT 參數1 參數2```

- ### 底下說明四種情況
    |        | CMD            | ENTRYPOINT     |
    | ------ | -------------- | -------------- |
    | Case 1 | ```["ls"]```   | ```[]```       |
    | Case 2 | ```[]```       | ```["ls"]```   |
    | Case 3 | ```["ls"]```   | ```["echo"]``` |
    | Case 4 | ```["echo"]``` | ```["ls"]```   |

- ### Case1: CMD: ls
    - Dockerfile
        ```dockerifle
        FROM nginx
        CMD ["/bin/ls"]
        ENTRYPOINT []
        ```
        - 入口點程式：```/bin/ls```
    - build image
        ```bash
        $ docker build -t nginx-by-tj .
        ```
    - test image
        ```bash
        # 入口點程式：/bin/ls
        $ docker run --rm --net=host nginx-by-tj
        bin
        boot
        dev
        docker-entrypoint.d
        docker-entrypoint.sh
        etc
        home
        lib
        lib64
        media
        mnt
        opt
        proc
        root
        run
        sbin
        srv
        sys
        tmp
        usr
        var
        
        # 入口點程式：pwd (pwd 取代 CMD)
        $ docker run --rm --net=host nginx-by-tj pwd
        /
        
        # 入口點程式：echo (echo 取代 CMD)
        $ docker run --rm --net=host nginx-by-tj echo 123
        123
        
        # 入口點程式：date (date 取代 CMD)
        $ docker run --rm --net=host nginx-by-tj date '+%T'
        Thu Dec 24 05:56:40 UTC 2020
        
        $ docker run --rm --net=host nginx-by-tj date '+%Y/%m/%d'
        2020/12/24
        
        # 入口點程式：cat (cat 取代 CMD)
        $ docker run --rm --net=host nginx-by-tj cat /usr/share/nginx/html/index.html
        ...
        <h1>Welcome to nginx!</h1>        
        ...
        ```
- ### Case2: ENTRYPOINT: ls
    - Dockerfile
        ```dockerifle
        FROM nginx
        CMD []
        ENTRYPOINT ["/bin/ls"]
        ```
        - 入口點程式：```/bin/ls```
    - build image
        ```bash
        $ docker build -t nginx-by-tj .
        ```
    - test image
        ```bash
        # 入口點程式：/bin/ls
        $ docker run --rm --net=host nginx-by-tj
        bin
        boot
        dev
        docker-entrypoint.d
        docker-entrypoint.sh
        etc
        home
        lib
        lib64
        media
        mnt
        opt
        proc
        root
        run
        sbin
        srv
        sys
        tmp
        usr
        var
        
        # 入口點程式：/bin/ls -ls
        # 其中 -ls 是 ls 參數
        $ docker run --rm --net=host nginx-by-tj -ls
        total 72
        4 drwxr-xr-x   2 root root 4096 Dec  9 23:22 bin
        4 drwxr-xr-x   2 root root 4096 Nov 22 12:37 boot
        0 drwxr-xr-x   5 root root  340 Dec 24 06:03 dev
        4 drwxr-xr-x   1 root root 4096 Dec 15 20:20 docker-entrypoint.d
        4 -rwxrwxr-x   1 root root 1202 Dec 15 20:20 docker-entrypoint.sh
        4 drwxr-xr-x   1 root root 4096 Dec 24 06:03 etc
        4 drwxr-xr-x   2 root root 4096 Nov 22 12:37 home
        4 drwxr-xr-x   1 root root 4096 Dec 15 20:20 lib
        4 drwxr-xr-x   2 root root 4096 Dec  9 23:22 lib64
        4 drwxr-xr-x   2 root root 4096 Dec  9 23:22 media
        4 drwxr-xr-x   2 root root 4096 Dec  9 23:22 mnt
        4 drwxr-xr-x   2 root root 4096 Dec  9 23:22 opt
        0 dr-xr-xr-x 321 root root    0 Dec 24 06:03 proc
        4 drwx------   2 root root 4096 Dec  9 23:22 root
        4 drwxr-xr-x   3 root root 4096 Dec  9 23:22 run
        4 drwxr-xr-x   2 root root 4096 Dec  9 23:22 sbin
        4 drwxr-xr-x   2 root root 4096 Dec  9 23:22 srv
        0 dr-xr-xr-x  13 root root    0 Dec  2 16:11 sys
        4 drwxrwxrwt   1 root root 4096 Dec 15 20:20 tmp
        4 drwxr-xr-x   1 root root 4096 Dec  9 23:22 usr
        4 drwxr-xr-x   1 root root 4096 Dec  9 23:22 var

        # 入口點程式：/bin/ls pwd
        # 其中 pwd 是 ls 的參數
        $ docker run --rm --net=host nginx-by-tj pwd
        /bin/ls: cannot access 'pwd': No such file or directory
        
        # 入口點程式：/bin/ls /bin/pwd
        # 其中 pwd 是 ls 的參數
        $ docker run --rm --net=host nginx-by-tj /bin/pwd
        /bin/pwd
        
        # 入口點程式：/bin/ls pwd
        # 其中 pwd 是 ls 的參數
        $ docker run --rm --net=host nginx-by-tj /bin/echo 123
        /bin/ls: cannot access '123': No such file or directory
        /bin/echo
        ```

- ### Case3: CMD: ls + ENTRYPOINT: echo
    - Dockerfile
        ```dockerifle
        FROM nginx
        CMD ["/bin/ls"]
        ENTRYPOINT ["/bin/echo"]
        ```
        - 入口點程式：```/bin/echo /bin/ls```
        - 其中，```/bin/ls``` 是 echo 參數
    - build image
        ```bash
        $ docker build -t nginx-by-tj .
        ```
    - test image
        ```bash
        # 入口點程式：/bin/echo /bin/ls
        # 其中 /bin/ls 是 echo 的參數
        $ docker run --rm --net=host nginx-by-tj
        /bin/ls
        # 注意：這邊在啟動 image ，沒有帶任何額外參數喔
        
        
        # 入口點程式：/bin/echo hello world
        # 其中 hello world 取代 CMD，並作為 echo 的參數
        $ docker run --rm --net=host nginx-by-tj hello world
        hello world
        ```

- ### Case4: CMD: echo + ENTRYPOINT: ls
    - Dockerfile
        ```dockerifle
        FROM nginx
        CMD ["/bin/echo"]
        ENTRYPOINT ["/bin/ls"]
        ```
        - 入口點程式：```/bin/ls /bin/echo```
        - ```/bin/echo``` 是 ls 參數
    - build image
        ```bash
        $ docker build -t nginx-by-tj .
        ```
    - test image
        ```bash
        # 入口點程式：/bin/ls /bin/echo
        # 其中 /bin/echo 是 ls 的參數
        $ docker run --rm --net=host nginx-by-tj
        /bin/echo
        # 注意：這邊在啟動 image ，沒有帶任何額外參數喔
        
        # 入口點程式：/bin/ls nginx-by-tj /usr/share/nginx/html
        # 其中 '/usr/share/nginx/html' 取代 CMD，並作為 ls 疵涮 
        $ docker run --rm --net=host nginx-by-tj /usr/share/nginx/html
        50x.html
        index.html
        
        $ docker run --rm --net=host nginx-by-tj /usr/share/nginx/html -ls
        total 8
        4 -rw-r--r-- 1 root root 494 Dec 15 13:59 50x.html
        4 -rw-r--r-- 1 root root 612 Dec 15 13:59 index.html
        ```

- ### 其他更複雜的 case
    - [[Docker 學習筆記] ENTRYPOINT 和 CMD 的分別和用法](https://blog.tiger-workshop.com/docker-difference-between-entrypoint-and-cmd/)
        ![](https://i.imgur.com/UuzuFxh.png)
        - 原則
            - 沒加 `[...]` 就會補上 prefix (`/bin/sh -c`)
        - Dockerfile
            ```dockerifle
            FROM nginx
            CMD /bin/ls
            ENTRYPOINT ["/bin/echo"]
            # 不加雙引號，執行時會有錯誤：
            # /bin/sh: 1: /bin/sh: [/bin/echo]: not found
            ```
            ```
            $ docker run --rm --net=host nginx-by-tj 
            /bin/sh -c /bin/ls
            ```
            
### 覆寫 ENTRYPOINT
- [Docker: Cannot execute binary file](https://stackoverflow.com/questions/61055324/docker-cannot-execute-binary-file)
    ```
    ENTRYPOINT [ "/bin/bash", "-l", "-c" ]
    ```
- [Is there an easy way to automatically run a script whenever I (re)start a container?](https://stackoverflow.com/questions/56540037/)

### 參考資料
- [[官方] ENTRYPOINT](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#entrypoint)
    > The best use for ENTRYPOINT is to set the image’s main command


<br>

## docker: ENTRYPOINT+CMD v.s. k8s: command+args
- [Dockerfile entrypoint in Kubernetes not executed](https://stackoverflow.com/questions/67113409)
    ```
          containers:
          - name: my-app
            imagePullPolicy: Always
            image: my-docker-image
            command: ["/bin/bash"]
            args: ["-c","./entrypoint.sh;while true; do echo hello; sleep 10;done"]
    ```
    相當於：
    ```
    docker run --entrypoint /bin/bash my-docker-image ...args here...
    ```
    - [Define a Command and Arguments for a Container](https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/)
        ![](https://i.imgur.com/BvQR8PV.png)

<br>

## VOLUME
- 功能
    - 讓 container 可以把資料儲存到 container 外部的儲存空間
- 目的/需求
    - 當 container 需要寫入大量資料或保存資料時，需要將資料寫入到外部空間
    - 減少 container 因不斷寫入資料，而造成容量過大的問題
    - 因為 container 的檔案系統是使用疊層(layer)的方式儲存

<br>

## WORKDIR
> 切到指定的目錄

```dockerfile=
WORKDIR /home
RUN pwd

WORKDIR /tmp
RUN pwd

WORKDIR /usr/local/bin/
RUN pwd
```
執行結果
```
/home
/tmp
/usr/local/bin
```

<br>

<hr>

<br>

# 問題排解
## memory, RAM
- ### 查詢容器的記憶體配額
    ```
    $ cat /sys/fs/cgroup/memory/memory.limit_in_bytes
    68719476736
    ```
    = 64 x 1024 x 1024 x1024
    = 64GiB
    - [Check mem_limit within a docker container](https://stackoverflow.com/a/42187357/4359712)

- ### 查詢容器的記憶體使用量
    ```
    $ cat /sys/fs/cgroup/memory/memory.usage_in_bytes
    303104
    $ cat /sys/fs/cgroup/memory/memory.limit_in_bytes
    9223372036854771712
    ```
    - 大數字表示沒有設限
    - 參考資料：
        - [Getting memory usage in Linux and Docker](https://shuheikagawa.com/blog/2017/05/27/memory-usage/)

<br>

## CPU
- [How to check the number of cores used by docker container?](https://stackoverflow.com/questions/47545960/)
    ```
    cat  /sys/fs/cgroup/cpuset/cpuset.cpus
    ```

<br>

## performance
- container ID -> process ID
    ```
    $ docker top <container_id>
    ```
    [![](https://hackmd.io/_uploads/SyjTxv6Ip.png)](https://hackmd.io/_uploads/SyjTxv6Ip.png)
    
    [![](https://hackmd.io/_uploads/ByCVbDp8T.png)](https://hackmd.io/_uploads/ByCVbDp8T.png)


<br>

<hr>

<br>


# 問題排解
## Permission denied
- 緣由
    ```bash
    $ docker run hello-world
    ```
- 症狀
    > docker: Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.39/containers/create: dial unix /var/run/docker.sock: connect: permission denied.
    > See 'docker run --help'.
- 根本原因
    - permission denied
- 解法1
    - 每次都加 sudo
- 解法2：可參考底下兩篇文章
    - [How can I use docker without sudo?](https://askubuntu.com/questions/477551/how-can-i-use-docker-without-sudo/477554#477554)
    - [[解決方法] docker: permission denied](http://andy51002000.blogspot.com/2019/02/docker-permission-denied.html)
    - 實際測試，底下三種方法皆可
        ```bash
        $ sudo gpasswd -a $USER docker
        $ sudo usermod -a -G docker $USER
        # or sudo usrmod -aG docker $USER
        # 
        # -G, --groups GROUPS
        #    new list of supplementary GROUPS
        # -a, --append
        #    append the user to the supplemental GROUPS 
        #    mentioned by the -G option without removing
        #    him/her from other groups

        # 自行添加 user 到 docker group
        # 變更前：docker:x:129:
        # 變更後：docker:x:129:tj
        $ sudo gedit /etc/group
        ```
        - 需要 logout 才會生效
- 正常執行
    ![](https://i.imgur.com/6VCHclT.png)

<br>

## [Cannot create cluster by kind 0.3.0 with docker 18.09.6 #567](https://github.com/kubernetes-sigs/kind/issues/567)
- how to
    ```--loglevel=debug``` to get more detail.
    ```sudo iptables -L -t nat```
- [No DOCKER chain in iptables #1871](https://github.com/moby/moby/issues/1871)
    ```
    sudo systemctl restart docker.service
    ```
    ![](https://i.imgur.com/neNmAcJ.png)
- ipatables 的正常情況
    ```
    sudo iptables --list --table nat
    # or
    sudo iptables -L -t nat
    ```
    ![](https://i.imgur.com/N3Ktb60.png)

<br>

## [Expose all ports for a Docker image](https://stackoverflow.com/questions/49323225/expose-all-ports-for-a-docker-image)
- ```--network host``` (或是```--net=host```)
- ```-P``` or ```--publish-all=true``` 
    (```--publish-all true``` ... :X:)
- ### docker pull image 失敗
```
docker pull nginx
WARNING: Error loading config file: /home/ocis-tn/.docker/config.json: open /home/ocis-tn/.docker/config.json: permission denied
Using default tag: latest
Error response from daemon: Get https://registry-1.docker.io/v2/: dial tcp 54.236.131.166:443: connect: network is unreachable
```

<br>

## [How do I assign a port mapping to an existing Docker container?](https://stackoverflow.com/questions/19335444/how-do-i-assign-a-port-mapping-to-an-existing-docker-container)
- 查看方式：
    ```bash
    $ sudo cat /var/lib/docker/containers/10e2d7f4890217ee42dba42f85c5c8a36ee009cb5ecf7a507d04d180228a1e12/hostconfig.json | python3 -m json.tool
    ```
- host:8082 -> container:80
    ```json
        "PortBindings": {
            "80/tcp": [
                {
                    "HostIp": "",
                    "HostPort": "8082"
                }
            ]
        },
    ```
- gcr.io/kuar-demo/kuard-amd64:1
    host:8083 -> container:8080
    - config.v2.json
        ```json
        "Config": {
            ...
            "ExposedPorts": {
                "8080/tcp": {}
            },
            ...
        },
        ...
        "NetworkSettings": {
            ...
            "Ports": {
                "8080/tcp": [
                    {
                        "HostIp": "0.0.0.0",
                        "HostPort": "8083"
                    }
                ]
            },
        ```
    - hostconfig.json
        ```json
            "NetworkMode": "default",
            "PortBindings": {
                "8080/tcp": [
                    {
                        "HostIp": "",
                        "HostPort": "8083"
                    }
                ]
            },
            "PublishAllPorts": false
        ```
        - [Docker Change Port Mapping for an Existing Container](https://mybrainimage.wordpress.com/2017/02/05/docker-change-port-mapping-for-an-existing-container/)
- nginx
    host <-> container
    - config.v2.json
        ```json
        "Config": {
            ...
            "ExposedPorts": {
                "80/tcp": {}
            },
        ...
        "NetworkSettings": {
            ...
            "Ports": {},
        ```
    - hostconfig.json
        ```json
        "NetworkMode": "host",
        "PortBindings": {},
        "PublishAllPorts": false,
        ```

<br>

## [Docker is returning an i/o timeout](http://docs.quay.io/issues/iotimeout.html)

### symptom
```bash
$ sudo docker pull ...
FATA[0021] Error response from daemon: v1 ping attempt failed with error: Get https://quay.io/v1/_ping: dial tcp: i/o timeout.
```

<br>

## [docker client 使用不当造成 too many open files](https://www.jianshu.com/p/0adb8147555c)
- 查看 FD lsof /var/run/docker.sock | wc -l 会发现，每次 docker start 并 ctrl-c 后，FD 计数会 +1 。因 start 操作发送给 dockerd 后dockerd会阻塞该请求直到插件可用。

- 在程序中，docker start 基于 context timeout，如果超时，则取消start 请求，等待数秒后在此start，如此，在很长一段时间后FD逐渐超标。直至达到限制。

<br>

## [24 个常见的 Docker 疑难杂症处理技巧](https://www.51cto.com/article/705804.html)
1. [Docker 迁移存储目录](https://www.51cto.com/article/705804.html#h26976cb-6Un6ohTH)
2. [Docker 设备空间不足](https://www.51cto.com/article/705804.html#h0613ccc-0UNXQ2Pc)
3. [Docker 缺共享链接库](https://www.51cto.com/article/705804.html#h0613ccc-Vq04AaaW)
4. [Docker 容器文件损坏](https://www.51cto.com/article/705804.html#h0613ccc-HNOONIRP)
5. [Docker 容器优雅重启](https://www.51cto.com/article/705804.html#h0613ccc-OE0ISb7L)
6. [Docker 容器无法删除](https://www.51cto.com/article/705804.html#h0613ccc-mKnmbZQV)
7. [Docker 容器中文异常](https://www.51cto.com/article/705804.html#h0613ccc-AddPDI6N)
8. [Docker 容器网络互通](https://www.51cto.com/article/705804.html#h0613ccc-1kDirCLV)
9. [Docker 容器总线错误](https://www.51cto.com/article/705804.html#h0613ccc-RgsgPXV1)
10. [Docker NFS 挂载报错](https://www.51cto.com/article/705804.html#h0613ccc-etYMvA4w)
11. [Docker 使用默认网段](https://www.51cto.com/article/705804.html#h0613ccc-TZTd10Cn)
12. [Docker 服务启动串台](https://www.51cto.com/article/705804.html#h0613ccc-Vnc1sllq)
13. [Docker 命令调用报错](https://www.51cto.com/article/705804.html#h0613ccc-OSGfulPf)
14. [Docker 定时任务异常](https://www.51cto.com/article/705804.html#h0613ccc-Q5o2RLqQ)
15. [Docker 变量使用引号](https://www.51cto.com/article/705804.html#h0613ccc-xDVS55Zp)
16. [Docker 删除镜像报错](https://www.51cto.com/article/705804.html#h0613ccc-gQvDCjGh)
17. [Docker 普通用户切换](https://www.51cto.com/article/705804.html#h0613ccc-sgYlv9Sf)
18. [Docker 绑定到 IPv6 上](https://www.51cto.com/article/705804.html#h0613ccc-TzDjmPAs)
19. [Docker 容器启动超时](https://www.51cto.com/article/705804.html#h0613ccc-AGeaXt7T)
20. [Docker 端口网络限制](https://www.51cto.com/article/705804.html#h0613ccc-y6mDuo9g)
21. [Docker 无法获取镜像](https://www.51cto.com/article/705804.html#h0613ccc-laL5ful7)
22. [Docker 使容器不退出](https://www.51cto.com/article/705804.html#h0613ccc-Bzo0WLsW)
23. [Docker 不使用默认网段](https://www.51cto.com/article/705804.html#h0613ccc-MhDlZOJS)
24. [Docker 添加私有仓库](https://www.51cto.com/article/705804.html#h0613ccc-A3xzNcvQ)
