Docker を使ってみたのさらに続き

# はじめに

Docker を使ってみました。Dockerfile や Docker Compose の使い方まで分かってきました。

[Docker を使ってみたの続き #Docker - Qiita](https://qiita.com/tinymouse/items/659b959efe177bcdb0ff)

その続きです。

# Docker Compose を使ってみる

Docker Compose をさらに使ってみようと思います。

## Linux マシンを用意する

Docker は Linux OS で稼動します。そのため Linux マシンを用意します。
自分は Google Cloud の Compute Engine を使って Ubuntu OS の仮想マシンを作りました。

[Google Compute Engine に Ubuntu デスクトップ環境を作ってリモート接続してみた #GoogleCloud - Qiita](https://qiita.com/tinymouse/items/ac1b26a985b82e44465f)

## Docker エンジンをインストールする

Ubuntu OS マシンに Docker エンジン、Docker Compose をインストールします。

[Docker Engine インストール（Ubuntu 向け） | Docker ドキュメント](https://matsuand.github.io/docs.docker.jp.onthefly/engine/install/ubuntu/)

[Compose プラグインをインストールする | ドッカードキュメント](https://docs-docker-com.translate.goog/compose/install/linux/?_x_tr_sl=en&_x_tr_tl=ja&_x_tr_hl=ja&_x_tr_pto=wapp)

## Python を使えるコンテナを作ってみる

Docker のいいところは、特定の機能が予め導入されたイメージを使ってコンテナを作って起動するだけで、その機能が使える実行環境が手に入るところだと言います。

Python を使えるコンテナを作ってみましょう。

### 作業ディレクトリと設定ファイルを用意する

作業ディレクトリを用意します。

```shell
$ mkdir trypython
$ cd trypython
```

`Dockerfile` を用意します。リポジトリから `Python` のイメージを入手します。↓

```dockerfile
FROM python

USER root

RUN apt -y update && \
    apt -y upgrade
```

`docker-compose.yml` を用意します。上記の `Dockerfile` を使ってイメージをビルドします。↓

```yaml
version: '2'
services:
    python:
        build: .
        container_name: 'trypython'
        working_dir: /root/app
        volumes:
            - ./app:/root/app
```

Docker 外部のディレクトリをバインドしておきます。↑

### コンテナを起動する

コンテナを起動します。

```shell
$ docker compose up
```

`docker compose` のコマンドについて確認します。

参考：[docker-compose 'up' とか 'build' とか 'start' とかの違いを理解できていなかったのでまとめてみた #Docker - Qiita](https://qiita.com/tegnike/items/bcdcee0320e11a928d46)

### コンテナなどの状態を確認する

作成されたイメージを確認してみます。

```shell
$ docker images
REPOSITORY         TAG       IMAGE ID       CREATED        SIZE
trypython-python   latest    4bf1a63cb90a   a minute ago   1.04GB
```

イメージが作成されています。名称は自動でセットされています。指定することもできるようです。

参考：[Docker Composeでビルド時に任意のイメージ名を指定する方法 - 水底](https://amaya382.hatenablog.jp/entry/2017/04/03/034002) 

コンテナの状態を確認します。

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS                    PORTS     NAMES
eac6cd860107   trypython-python   "python3"     a minute ago   Exited (0) a second ago             trypython
```

コンテナが作成されています。名称は指定したものになっています。

参考：[docker-composeのコンテナ名のデフォルト名は「プロジェクト名-サービス名-インデックス番号」 - コード日進月歩](https://shinkufencer.hateblo.jp/entry/2022/03/19/000000)

ステータスは `Exited` になっています。つまり停止しています。↑
`Exited` なので、起動できなかったわけではなく、起動して直ぐに終了していることになります。

直ぐに終了してしまっては、起動した意味がありません。
コンテナ型である Docker と比較されるハイパーバイザ型の Hyper-V は、仮想マシンが起動するとユーザが終了するまで実行し続けます。
Docker のコンテナは、なぜ同じでないのでしょうか。

### コンテナを後始末する

ひとまずコンテナを後始末しておきます。

```shell
$ docker compose down
```

コンテナの状態を確認します。

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS                    PORTS     NAMES
```

コンテナはなくなっています。↑

参考：[docker-compose downとstop #Docker - Qiita](https://qiita.com/yusuke_mrmt/items/e05d7914065824384a6b)

## コンテナで Python プログラムを実行する

上記の Python 環境でプログラムを実行してみましょう

### プログラムを用意する

実行する Python プログラムを用意します。

```python:app/hello.py
print("Hello, Python.")
```

作業ディレクトリに `app` ディレクトリが作成されています。`docker-compose.yml` でコンテナの `/root/app` にバインドしています。ここに `hello.py` を用意します。↑

### コンテナの起動時にプログラムを実行する

`docker-compose.yml` に追記します。

```yaml
(前略)
        command: python hello.py
```

参考：[Dockerfileに記載するCMD命令とdocker-compose.ymlのcommandの使い分け #初心者 - Qiita](https://qiita.com/soicchi/items/0bf3f97aaee245fa252b)

### コンテナを起動する

コンテナを起動します。

```shell
$ docker compose up
[+] Running 1/0
 ✔ Container trypython  Created                                                                                                                                                                          0.0s 
Attaching to trypython
trypython  | Hello, Python.
trypython exited with code 0
```

プログラムの実行結果が表示されました。↑

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND            CREATED         STATUS                        PORTS                                      NAMES
67d5049862e0   trypython-python   "python hello.…"   a minutes ago   Exited (0) a second ago                                                  trypython
```

コンテナは停止してます。↑

指定したプログラムを実行して、それが終わるとコンテナも終了して、呼出側に戻るようにしていると考えられます。

## Python でウェブサーバを起動する

Python で簡易的なウェブサーバを構築してみましょう。

参考：[pythonでローカルwebサーバを立ち上げる #Python - Qiita](https://qiita.com/okhrn/items/4d3c74563154f191ba16)

### 作業ディレクトリと設定ファイルを用意する

作業ディレクトリを用意します。

```shell
$ mkdir tryhttpd
$ cd tryhttpd
```

`Dockerfile` は同じものを使います。↓

```dockerfile
FROM python

USER root

RUN apt -y update && \
    apt -y upgrade
```

`docker-compose.yml` を用意します。`ports` の指定を加えます。↓

```yaml
version: '2'
services:
    httpd:
        build: .
        container_name: 'tryhttpd'
        working_dir: /root/www
        volumes:
            - ./www:/root/www
        ports:
            - 8000:8000
        command: python httpd.py
```

Docker 外部のディレクトリをバインドしています。
コンテナを実行したとき、このディレクトリが作業ディレクトリになるようにしておきます。↑

参考：[docker composeのworking_dirオプションとは？ - fleblog～IT転職・プログラミング～](https://footloose-engineer.com/docker-compose-working-dir/)

そのディレクトリに HTML ファイルを用意します。

```html:www/index.html
<html>
<body>
Hello, Python.
</body>
</html>
```

実行する Python プログラムを用意します。

```python:www/httpd.py
import http.server
import socketserver

PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("http serving at port", PORT)
    httpd.serve_forever()
```

### コンテナを起動する

コンテナを起動します。

```shell
$ docker compose up
[+] Running 1/1
 ✔ Container tryhttpd  Created                                                                                                                                                                           0.0s 
Attaching to tryhttpd
```

コマンドプロンプトに戻りません。
`hello.py` と違って `httpd.py` は、プログラムが直ぐに終了しないで待機するようになっています。

ここで Docker を実行しているマシンでウェブブラウザを開きます。

```
http://localhost:8000
```

ブラウザ画面に `Hello, Python.` が表示されました。

Docker の実行画面を確認します。

```shell
tryhttpd  | 172.20.0.1 - - [10/Aug/2024 00:00:00] "GET / HTTP/1.1" 304 -
```

Python プログラムが出力した内容のようです。↑

ここで [Ctrl]＋[C] 押下します。

```shell
[+] Stopping 1/1
 ✔ Container tryhttpd  Stopped                                                                                                                                                                          10.0s 
canceled
```

Python プログラムが終了します。併せてコンテナも停止しました。

## IDE でスクリプトを編集して実行する

上記で使用したスクリプトくらいならいいのですが、規模が大きくなるとソースコードを編集したりデバッグするのが大変になってきます。IDE（統合開発環境）を使ってソースコードを編集したり実行してデバッグしたりしたいものです。

### Visual Studio Code をインストールする

スクリプトを編集したりデバッグするのに Visual Studio Code (VS Code) を使うことにします。Docker を実行しているマシンにインストールします。

[UbuntuでVSCode（日本語版にもできるよ）を使う | BLOGs win](https://blog.winproject.jp/ubuntu-vscode-japanese-language.html)

### VS Code で Docker コンテナを操作できるようにする

VS Code に `Docker 拡張機能` を導入します。設定ファイルを編集したりコンテナを操作する作業を支援する機能が使えます。

[VS Code の Docker 拡張機能の使い方 - LAZE SOFTWARE](https://lazesoftware.com/ja/blog/230306/)

### 作業ディレクトリと設定ファイルを用意する

作業ディレクトリを用意します

```shell
$ mkdir tryvscode
$ cd tryvscode
```

`Dockerfile` は同じものを使います。↓

```dockerfile
FROM python

USER root

RUN apt -y update && \
    apt -y upgrade
```

`docker-compose.yml` は最初に Python 環境を作ったときのものを流用します。

```yaml
version: '2'
services:
    python:
        build: .
        container_name: 'tryvscode'
        working_dir: /root/app
        volumes:
            - ./app:/root/app
```

### コンテナを起動させ続ける指定する

コンテナが直ぐに終了しないで起動し続けるようにしたいと思います

参考：[Dockerコンテナをずっと起動しておく - WEB開発ノート](https://utouto97.hatenablog.com/entry/2022/03/15/092924)

`docker-compose.yml` に追記します。

```yaml
(前略)
        tty: true
        stdin_open: true
(後略)
```

### コンテナを起動する

コンテナを起動します。

```shell
$ docker compose up
[+] Running 2/2
 ✔ Network tryvscode_default  Created                                                       0.0s 
 ✔ Container tryvscode        Started                                                       0.0s 
```

### VS Code で Docker コンテナに接続する

VS Code を起動して上記の Docker コンテナに接続します。

[【Visual Studio Code】Dockerコンテナに接続しよう（Dev Containersの使い方） | ひろぴよのDX日記](https://it-dxblog.com/vscode-remote-containers/)

起動しているコンテナ内部のフォルダを開いてファイルを作成したり編集できます。

### VS Code で Python プログラミング

VS Code に `Python 拡張機能` を導入します。Python スクリプトを編集したり実行したりデバッグする作業を支援する機能が使えます。

[Windows 11 の VSCode の Python 開発環境の構築 #初心者向け - Qiita](https://qiita.com/mmake/items/55401c6a9e2f3f0f3475)

Python ファイルを開いてデバッグ実行できます。

# Docker の挙動について理解する

## コマンドラインシェルからコンテナに接続する

起動しているコンテナに VS Code で接続して操作できました。コマンドラインシェルから接続して操作してみたいと思います。

### 作業ディレクトリと設定ファイルを用意する

作業ディレクトリを用意します。

```shell
$ mkdir trydocker
$ cd trydocker
```

`Dockerfile` は同じものを使います。

```dockerfile
FROM python

USER root

RUN apt -y update && \
    apt -y upgrade
```

`docker-compose.yml` は最後に VS Code 用に作ったときのものを流用します。

```yaml
version: '2'
services:
    python:
        build: .
        container_name: 'trydocker'
        hostname: 'trydocker'
        tty: true
        stdin_open: true
        working_dir: /root/app
        volumes:
            - ./app:/root/app
```

`hostname` を指定しておきます。↑

## -d オプションについて

`compose up` や `exec /bin/bash` するときに `-d` を指定することがあります。これは何でしょうか。

参考：[dockerの-dオプション、デタッチドモードって何？ #Docker - Qiita](https://qiita.com/keitean/items/d83254090405aa714b15) 

### コンテナを起動して接続する①

コンテナを起動します。

```shell
$ docker compose up
[+] Running 2/2
 ✔ Network trydocker_default  Created                                                                                            0.1s 
 ✔ Container trydocker        Created                                                                                            0.1s 
Attaching to trydocker
trydocker  | Python 3.12.5 (main, Aug 10 2024, 00:00:00) [GCC 12.2.0] on linux
trydocker  | Type "help", "copyright", "credits" or "license" for more information.
```

キー入力しても応答ありません。

[Ctrl]＋[C] すると終了します。

```shell
[+] Stopping 1/1
 ✔ Container trydocker  Stopped                                                                                                 10.2s 
canceled
```

`-d` を指定してコンテナを起動します。コンテナがバックグラウンドで起動するようになります。

```shell
$ docker compose up -d
[+] Running 2/2
 ✔ Network trydocker_default  Created                                                                     0.0s 
 ✔ Container trydocker        Started                                                                     0.0s 
```

コマンドプロンプトに戻ってきます。コンテナの状態を確認します。

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND     CREATED        STATUS               PORTS     NAMES
891bd027da43   trydocker-python   "python3"   a minute ago   Up a second ago             trydocker
```

コンテナは起動しています。このコンテナに接続します。

```
$ docker attach trydocker
>>>
```

Python の対話モードのようです。Pyhon コードを入力してみます。キー入力できて応答します。

```
>>> print("Hello, Python")
Hello, Python
```

[Ctrl]＋[D] で終了します。

```shell
CONTAINER ID   IMAGE              COMMAND     CREATED        STATUS                     PORTS     NAMES
891bd027da43   trydocker-python   "python3"   a minute ago   Exited (0) a second ago             trydocker
```

コンテナが停止しています。↑

## コンテナに接続したときに起動しているプログラム

`docker attach` したときコンテナのコマンドラインシェルを使えるのかと思っていたのですが、そうではありませんでした。コンテナを起動したときに実行されているプログラムが使えることになります。
`python` イメージで作成したコンテナは、`python3` コマンドが実行されるよう設定されていることが分かります。

コンテナの起動時に `/bin/bash` を実行するよう指定したいと思います。

### コンテナを起動して接続する②

`docker-compose.yml` に追記します。

```yaml
(前略)
        command: /bin/bash
```

コンテナを起動します。

```shell
$ docker compose up -d
[+] Running 1/1
 ✔ Container trydocker  Started
```

コンテナの状態を確認します。

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS         PORTS     NAMES
86a2e0095089   trydocker-python   "/bin/bash"   a minute ago   Up a second                   trydocker
```

コンテナに接続します。

```shell
$ docker attach trydocker
root@trydocker:~# 
```

シェル対話モードになりました。コマンドを入力してみます。キー入力できて応答します。

```shell
root@trydocker:~# cat /etc/*release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
(以下略)
```

`exit` で終了します。

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS                     PORTS     NAMES
86a2e0095089   trydocker-python   "/bin/bash"   a minute ago   Exited (0) a second ago              trydocker
```

コンテナが停止しています。↑

## docker attach と docker exec の違い

コンテナに接続するのに、`attach` でなく `exec /bin/bash` するように書かれている記事があります。どう違うのでしょうか。

参考：[Dockerコンテナ内で操作 attachとexecの違い #Docker - Qiita](https://qiita.com/RyoMa_0923/items/9b5d2c4a97205692a560)

### コンテナを起動して接続する③

コンテナを起動します。

```shell
$ docker compose up -d
[+] Running 1/1
 ✔ Container trydocker  Started
```

コンテナに接続します。`exec /bin/bash` します。

```shell
$ docker exec -it trydocker /bin/bash
root@trydocker:~# 
```

シェル対話モードになりました。コマンドを入力してみます。キー入力できて応答します。

```shell
root@trydocker:~# cat /etc/*release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
(以下略)
```

`exit` で終了します。

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS           PORTS     NAMES
86a2e0095089   trydocker-python   "/bin/bash"   a minute ago   Up About a minute             trydocker
```

コンテナは起動しています。↑

### コンテナを起動して接続する④

起動しているコンテナに接続します。

```shell:ターミナル①
$ docker attach trydocker
root@trydocker:~# 
```

別のターミナルを起動して同じコンテナに接続します。

```shell:ターミナル②
$ docker attach trydocker
root@trydocer:~# 
```

一方のターミナルで入力して出力された結果が、別のターミナルで表示されます。↓

```shell:ターミナル①
root@trydocker:~# cat /etc/*release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
(以下略)
```
```shell:ターミナル②
root@trydocker:~# cat /etc/*release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
(以下略)
```

どちらかのターミナルで `exit` します。

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS                     PORTS     NAMES
86a2e0095089   trydocker-python   "/bin/bash"   a minute ago   Exited (0) a second ago              trydocker
```

コンテナが停止しています。↑

もう一度コンテナを起動します。

```shell
$ docker compose up -d
[+] Running 1/1
 ✔ Container trydocker  Started
```

一方のターミナルでコンテナに接続します。`attach` します。

```shell:ターミナル①
$ docker attach trydocker
root@trydocker:~# 
```

別のターミナルでコンテナに接続します。`exec /bin/bash` します。

```shell:ターミナル②
$ docker attach exec -it trydocker /bin/bash
root@trydocer:~# 
```

一方のターミナルで入力して出力された結果が、別のターミナルで表示されることはありません。

```shell:ターミナル①
root@trydocker:~# cat /etc/*release
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
(以下略)
```
```shell:ターミナル②
root@trydocker:~# 
```

`exec /bin/bash` したターミナルで `exit` してみます。

```shell:ターミナル②
root@trydocker:~# exit
```

コンテナの状態を確認します。

```shell:ターミナル②
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS           PORTS     NAMES
86a2e0095089   trydocker-python   "/bin/bash"   a minute ago   Up About a minute             trydocker
```

コンテナは起動しています。↑

もう一度 `exec /bin/bash` します。

```shell:ターミナル②
$ docker attach exec -it trydocker /bin/bash
root@trydocer:~# 
```

`attach` したターミナルで `exit` してみます。

```shell:ターミナル①
root@trydocker:~# exit
```

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS                     PORTS     NAMES
86a2e0095089   trydocker-python   "/bin/bash"   a minute ago   Exited (0) a second ago              trydocker
```

コンテナは停止しています。↑

## -it オプションについて

`exec /bin/bash` するときに `-it` を指定しました。これは何でしょうか。

参考：[「docker run -it」にするとコンテナがすぐに終了しない理由 - プログラミング初心者がアーキテクトっぽく語る](https://architecting.hateblo.jp/entry/2020/08/13/153842)

### コンテナを起動して接続する⑤

コンテナを起動します。

```shell
$ docker compose up -d
```

コンテナに接続します。`-it` を指定しないでみます。

```shell
$ docker exec trydocker /bin/bash
```

シェル対話モードになりません。

コンテナの状態を確認します。

```shell
$ docker ps -a
CONTAINER ID   IMAGE              COMMAND       CREATED        STATUS               PORTS     NAMES
891bd027da43   trydocker-python   "/bin/bash"   a minute ago   Up a minute ago             trydocker
```

コンテナは起動しています。
`/bin/bash` は実行されてもすぐに終了してしまったようです。