ちょっとわかった！Docker

# はじめに
Docker の面白さ・便利さに気づき始めたので、他の人にも伝えるべく記事を書きます。

:::note info
**（2025/03/30 追記）**
↓ 超勉強になった本。Docker のホストマシン上での動きが基礎から解説されています。まずこれを読みましょう。
[実践 Docker - ソフトウェアエンジニアの「Docker よくわからない」を終わりにする本](https://zenn.dev/suzuki_hoge/books/2022-03-docker-practice-8ae36c33424b59)
:::

# docker 単体系コマンド（compose じゃないやつ）
```tcl:一覧コマンド
# イメージ一覧
docker images
# コンテナ一覧
docker ps
docker ps -a
# 内部一覧
docker image inspect <イメージ名>
docker container inspect <コンテナ名>
docker volume inspect <ボリューム名>
```

```tcl:お掃除コマンド
# コンテナ全停止
docker stop $(docker ps -aq)
# イメージ・コンテナ・ボリューム一括全削除（ただし使用中のものは消せない）
docker system prune -a
# イメージ・コンテナ・ボリュームそれぞれ全削除
docker image rm $(docker image ls -aq)
docker container rm $(docker container ls -aq)
docker volume rm $(docker volume ls -q)
# 状態確認
docker system df
```

```tcl:コンテナ操作系
# Docker Hub からイメージを持ってくる
docker pull <イメージ名>
docker pull ubuntu:20.04

# 自分でイメージを作成する（Dockerfile と同階層でコマンドを実行）
docker build -t <任意のイメージ名> .
docker build -t my_image .

# イメージのレイヤー（構成要素）を確認
docker history <イメージID>

# イメージからコンテナを起動する（create + start）
docker run <イメージ名>
# ↓ これはすぐ停止状態になる（下のメモ参照）
docker run ubuntu:20.04
# ↓ これは起動状態を維持する
docker run -it ubuntu:20.04 bash
# ↓ これも維持
docker run --name <任意のコンテナ名> -p <任意のホスト側ポート番号>:<任意のコンテナ側ポート番号> <イメージ名>
docker run --name docker-getting-started -p 80:80 docker/getting-started

# コンテナを作るだけ（start待ち）
docker create <イメージ名>
docker create --name my_ubuntu_container ubuntu:20.04

# コンテナを一時停止・再開する
docker stop/start <コンテナ名>
docker start my_ubuntu_container

# 起動状態のコンテナの中に入る
# bash がダメなら sh など試す
docker exec -it <コンテナ名> bash
```

:::note info
**コンテナが run した瞬間停止状態になる理由**
大事なコンテナ原則
・コンテナはメインプロセスを実行するために起動する
・メインプロセスが終了したコンテナは自動で停止する
（[実践 Docker - ソフトウェアエンジニアの「Docker よくわからない」を終わりにする本](https://zenn.dev/suzuki_hoge/books/2022-03-docker-practice-8ae36c33424b59)より）

具体例として、「docker run ubuntu:20.04」でコンテナ作成＆起動はされるが、メインプロセスを指定していないので終了扱いですぐ停止される。
一方「docker run -it ubuntu:20.04 bash」はメインプロセスとして「bash」を指定しているので、私たちがコンテナ内で bash 操作している間は停止しない。bash から exit したらメインプロセス終了で停止される。

nginx イメージとか、ものによってはデフォルトでメインプロセスが指定されているものもあり、そういうのは起動した瞬間停止されることがない。
:::

<table>
  <caption>オプション例</caption>
  <thead>
    <tr>
      <th>オプション</th> <th>説明</th>
    </tr>
  </thead>
  <tr>
    <td> -d </td> <td>--detach。デタッチモード。コンテナがバックグラウンド実行され、ターミナルはブロックされない </td>
  </tr>
  <tr>
    <td> -p </td> <td> --publish。-p 5000:5001なら、ホストの5000番はコンテナ内の5001につながっている
 </td>
  </tr>
  <tr>
    <td> -f </td> <td> --force。起動中のコンテナを、停止を経由せずいきなり消すときに使う </td>
  </tr>
  <tr>
    <td> --rm </td> <td> コンテナが停止されたとき自動的にコンテナを削除する </td>
  </tr>
  <tr>
    <td> -e </td> <td> 環境変数（例：-e PASSWORD=password）</td>
  </tr>
  <tr>
    <td> -v </td> <td> ボリュームの設定（例：-v my-volume:/my-data）</td>
  </tr>
  <tr>
    <td> -it </td> <td> --interactive と --tty。コンテナに入るときによく使う </td>
  </tr>
  <tr>
    <td> --network </td> <td> 使用ネットワーク指定（例：--network networkname） </td>
  </tr>
</table>

# Dockerfile と compose.yaml を動かしてみる
全体像は[こちらのリポジトリ](https://github.com/TanakaLatias/my-containers/tree/main)を git clone して、docker compose build と docker compose up をしてご確認ください。

# Dockerfile の内容
```docker: Dockerfile
# FROM <イメージ名>
FROM node:17-alpine

RUN npm install -g nodemon

# これ以降コンテナ内では /app で作業する
WORKDIR /app

# ローカルの package.json を /app にコピー
COPY package*.json .

# /app で package*.json を参考にパッケージをインストール
RUN npm install

# ローカルのファイルを /app にコピー
COPY . .

# 使うポートを指定（任意）
EXPOSE 4000

# CMD で指定したコマンドは、上記のインストールなどが終わってから実行される
# RUN で指定するとインストール完了を待たずに実行されてしまう
CMD ["npm", "run", "dev"]
```

# compose.yaml の内容
```yaml:compose.yaml
services:

  backend:
    # .yml ファイルから見て ./api にある Dockerfile をイメージとしてビルド
    build: ./backend
    # コンテナ名
    container_name: backend_c
    # ローカルポート:コンテナポート
    # localhost:4000でコンテナの4000が見られる
    ports:
      - 4000:4000
    volumes:
      # ローカルファイル:コンテナファイル
      # バインドマウントなのでこの２つは連動する
      - ./backend/src:/app/src
    environment:
      # 環境変数は ./backend/src/db.js 内で使っている
      ENV: env_value
      POSTGRES_USER: p_username
      POSTGRES_PASSWORD: p_password
      POSTGRES_DB: p_database
    depends_on:
      # db のコンテナが起動してから backend が起動する
      - db
    links:
      # depends_on とセット？（なくても動くような…）
      - db
  
  frontend:
    build: ./frontend
    container_name: frontend_c
    ports:
      - 3000:3000
    volumes:
      - ./frontend/src:/app/src
    # --interactive
    stdin_open: true
    # --tty
    tty: true
  
  db:
    # Dockerfile からビルドせずイメージ名を直接指定
    image: postgres:latest
    container_name: db_c
    environment:
      POSTGRES_USER: p_username
      POSTGRES_PASSWORD: p_password
      POSTGRES_DB: p_database
    ports:
      - 5432:5432
    volumes:
      # バインドマウント
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
      # ボリュームマウント
      - db_data:/var/lib/postgresql/data
    # コンテナが常に再起動する（なくても動くような…）unless-stopped もある
    restart: always

volumes:
  db_data:
```


# compose.yaml 起動系コマンド
```tcl:ターミナル
# compose.yaml 起動系コマンド

# 起動準備をする
docker compose build
# 起動する
docker compose up
# 止める
docker compose stop
# 再開する
docker compose start
# 消す
docker compose down

# ファイル名指定（stop/start/down 時もファイル名を指定する）
docker compose -f docker-compose-one.yaml up
```

# Dockerfile その他にできること紹介
```docker: Dockerfile その他にできること紹介
RUN apt-get update && \
    apt-get install -y curl && \
    curl -sL https://deb.nodesource.com/setup_18.x | bash - && \
    apt-get upgrade -y && \
    apt-get install -y nodejs && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
# ↓ と同じ意味だが、横長で見づらいので「\」で改行する
RUN apt-get update && apt-get install -y curl && curl -sL https://deb.nodesource.com/setup_18.x | bash - && apt-get upgrade -y && apt-get install -y nodejs && apt-get clean && rm -rf /var/lib/apt/lists/*
# RUN RUN RUN とするとレイヤー数が増えてイメージが大きくなるので && でつないでまとめる
# まとめておくと、２回目以降キャッシュで一部 RUN されない由来のエラーも回避できる

# インストールも「\」で改行できる
RUN apt-get update && apt-get install -y \
  build-essential \
  libpq-dev \
  nodejs \
  postgresql-client \
  yarn

# app ユーザに /app の権限を付与し、root ユーザではなく app ユーザで作業する
RUN addgroup app && adduser -S -G app app
RUN chown -R app:app .
USER app
# root ユーザにも戻せる
USER root

# どちらの書き方でもいい
CMD ["npm", "run", "dev"]
CMD npm run dev

# CMD とどう違うのかわからない…
ENTRYPOINT [ "node", "main.js" ]

# ディレクトリ作成
RUN mkdir -p /home/app

# 環境変数設定
ENV FLASK_APP=app.py \
	FLASK_RUN_HOST=0.0.0.0 \
	FLASK_RUN_PORT=8000
```

# compose.yaml その他にできること紹介
```yaml:compose.yaml その他にできること紹介
services:  

  service-1:
    # Dockerfile の場所と名前を指定する
    build:
      context: service-1
      dockerfile: Dockerfile.env
      
    # コンテナ内で実行されているサービスが正常に動作しているか定期的に確認
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080"]
      interval: 5s
      retries: 5
      start_period: 15s
      timeout: 5s
    
    # docker compose watch すると指定のファイルに変更があった時 rebuild する
    # 単純に down と up すればいいのでは感も
    develop:
      watch:
        - path: ./service-1/package.json
          action: rebuild
    
    # Dockerfile の CMD や ENTRYPOINT の上書き
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    entrypoint: "/bin/sh -c 'npm install && npm run start'"

    # 使用する .env を指定
    env_file:
      - ./service-1/dev.env

    # 使うネットワークを指定
    networks:
      - coffee

# 使うネットワークを指定
# 普通に build と up すれば自動でネットワーク作成されるので、あえて指定するケースは少ないかも
networks:
  coffee:
    ipam:
      driver: default
      config:
        - subnet: "192.168.92.0/24"
```

# 関連：その他の compose.yaml オプション
<table>
  <thead>
    <tr>
      <th>オプション名</th> <th>内容</th>
    </tr>
  </thead>
  <tr>
    <td> configs:、secrets:、extends: </td> <td> ファイルを指定し、ファイル内の環境変数？を使えるようにする </td>
  </tr>
  <tr>
    <td> sync、rebuild、sync+restartなど </td> <td> 調べきれていない </td>
  </tr>
  <tr>
    <td> profiles: </td> <td> 調べきれていない </td>
  </tr>
</table>

# 関連：compose.yaml
- [composerize](https://www.composerize.com/)で docker run コマンドから compose.yaml を作成できる
- ボリュームマウントのファイルの保存場所は \\wsl.localhost のdocker-desctop-data 配下にある（気になる方は[こちらのYoutube](https://www.youtube.com/watch?v=cMyoSkQZ41E)を参照）
![Screenshot (244).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3047950/fb7c8b13-6f8f-320d-8fcd-ad26a43b3826.png)


# おわりに
Docker に関する知識を整理できてよかったです。
知らないオプション設定がたくさんあるのでまた必要に応じて勉強したい。

> 参考にさせていただいた記事
> - [Docker Crash Course Tutorial](https://www.youtube.com/watch?v=31ieHmcTUOk&list=PL4cUxeGkcC9hxjeEtdHFNYMtCpjNBm3h7)
> - [Docker In One Shot - Part 1 & 2](https://www.youtube.com/watch?v=31k6AtW-b3Y)
> - [Docker Compose will BLOW your MIND!! (a tutorial)](https://www.youtube.com/watch?v=DM65_JyGxCo) / [.yamlファイル例](https://github.com/vulhub/vulhub)
> - [Run Docker in Windows - Setup, Docker Compose, Extensions](https://www.youtube.com/watch?v=cMyoSkQZ41E)
> - [Docker Compose Tutorial](https://www.youtube.com/watch?v=HG6yIjZapSA)
> - [Docker Tutorial for Beginners](https://www.youtube.com/watch?v=pTFZFxd4hOI)
> - [Learn Docker in 1 Hour | Full Docker Course for Beginners](https://www.youtube.com/watch?v=GFgJkfScVNU)
> - [Ultimate Docker Compose Tutorial](https://www.youtube.com/watch?v=SXwC9fSwct8)
> - [Docker-compose tutorial](https://www.youtube.com/watch?v=qH4ZKfwbO8w)
> - [DOCKER COMPOSE | Complete Guide with Hands-On Examples](https://www.youtube.com/watch?v=uzq4Jy83xxw)
> - [Docker Compose Tutorial for Beginners (Networks - Volumes - Secrets - Postgres - Letsencrypt)](https://www.youtube.com/watch?v=YMBT1NguJJw)
など