Upgrade to Pro — share decks privately, control downloads, hide ads and more …

はじめての環境構築!デプロイ〜Docker基礎を学べるワークショップ!

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 はじめての環境構築!デプロイ〜Docker基礎を学べるワークショップ!

More Decks by ディップ株式会社

Other Decks in Technology

Transcript

  1. 今回のゴール 「Dockerとは何か」 「なぜ便利なのか」を自分の言葉で説明できる docker run / docker build / docker

    compose up の中で何が起きているかが分 かる Dockerfile と compose.yaml を読み・書きできる ビルドキャッシュを意識して、待ち時間の短いDockerfileを書ける 何か壊れたとき、自分でログを読んで原因を突き止められる Docker入門 / 技育アカデミア 2
  2. 目次 1. 仮想化の基礎 — なぜDockerが生まれたのか 2. Dockerとは何か — 公式定義とアーキテクチャ 3.

    コンテナとイメージ — 3つの登場人物 4. Docker CLI の使い方 5. Dockerfile とイメージビルド 6. ビルドキャッシュを味方につける 7. データとネットワーク 8. Docker Compose で複数コンテナを束ねる 9. まとめ Docker入門 / 技育アカデミア 3
  3. 「環境構築の苦しみ」を想像してみよう PHP 7.4 と 8.3 を 同じPCで共存させたい ローカルでは動くのに、本番(別バージョン)で動かない 新メンバーの環境構築に 3日かかる

    いつの間にか環境設定がズレてテストが当てにならない 共通の原因 → 「実行環境」がコードと一緒に運ばれていない それを解決するために、軽くて運びやすいコンテナ型仮想化が生まれた。 Docker入門 / 技育アカデミア 6
  4. 仮想化の2つの方式 ホスト型仮想化 ホストOSの上に「仮想化ソフト」を 入れる その中でゲストOSを丸ごと動かす 例: VirtualBox, VMware Fusion コンテナ型仮想化

    OSは用意せず、ホストOSを仕切っ て使う カーネルをホストと共有する 例: Docker, Podman Docker入門 / 技育アカデミア 7
  5. ホスト型 vs コンテナ型 観点 ホスト型仮想化 (VM) コンテナ型仮想化 (Docker) OSの持ち方 ゲストOSをまるごと持つ

    ホストOSのカーネルを共有 起動時間 数十秒〜数分 1〜数秒 サイズ 数GB 数十MB〜数百MB 隔離の強さ OSレベルで強い プロセスレベル 主な用途 OSごと検証・OS差吸収 アプリの実行環境を束ねる 「軽い」 「速い」 「環境ごと運べる」これがコンテナの特徴。 Docker入門 / 技育アカデミア 8
  6. Dockerを構成する3つの要素 Dockerfile イメージの設計図。 FROM node:20-alpine COPY . /app CMD ["node",

    "/app/index.js"] Image / Container Image: 設計図から作られた読み取り 専用テンプレート Container: イメージから起動した実 体 Docker入門 / 技育アカデミア 11
  7. 3要素のライフサイクル全体図 出典: GeeksforGeeks 1. Dockerfile をテキストで書く 2. docker build で

    Image を生成 3. docker push で Registry に登録 → 他環境が pull 4. docker run で Container が起動 5. docker stop / rm でコンテナを消 す(Imageは残る) Docker入門 / 技育アカデミア 12
  8. コンテナとは — 公式定義 A container is an isolated process for

    your application's component. (コンテナとは、アプリの構成要素のための 隔離されたプロセス である) 公式が挙げる4つの性質: Self-contained: 動作に必要なものをすべて内包 Isolated: ホスト・他コンテナと隔離されている Independent: 個別に管理・削除できる Portable: 開発PCでもクラウドでも同じように動く Docker入門 / 技育アカデミア 14
  9. レイヤを「見る」コマンド $ docker history node:20-alpine IMAGE CREATED CREATED BY SIZE

    abc123... 2 weeks ago CMD ["node"] 0B def456... 2 weeks ago ENTRYPOINT ["docker-entrypoint.sh"] 0B ... 2 weeks ago RUN addgroup -g 1000 node && adduser ... 87.3kB ... 3 weeks ago /bin/sh -c #(nop) ARG NODE_VERSION=20.11.1 0B ghi789... 3 weeks ago /bin/sh -c apk add --no-cache libstdc++ 3.46MB ... 4 weeks ago /bin/sh -c #(nop) ADD file:abc... in / 7.34MB 各行が 1レイヤ 「 apk add ... でX MB増えた」が見えるので、ダイエットの手がかりになる Docker入門 / 技育アカデミア 17
  10. イメージタグの読み方とベストプラクティス postgres:15.4-alpine │ │ └─ バリアント(alpine / slim など) │

    └───── バージョン └─────────────── イメージ名 パターン 使う場面 nginx / nginx:latest NG(再現性が壊れる) nginx:1.27 マイナー固定。開発で許容 nginx:1.27.0 パッチ固定。本番推奨 Docker入門 / 技育アカデミア 19
  11. 最初の一歩 — docker run hello-world $ docker run hello-world Hello

    from Docker! This message shows that your installation appears to be working correctly. 裏で起きていること: 1. ローカルに hello-world イメージがあるか確認 2. なければ Docker Hub からpull 3. イメージからコンテナを作成 4. コンテナの中でプログラムを実行("Hello from Docker!" を出力) 5. プログラム終了とともにコンテナも停止 Docker入門 / 技育アカデミア 21
  12. 主要コマンド コマンド 役割 docker run <image> イメージからコンテナを起動 docker ps 動作中のコンテナ一覧

    docker ps -a 停止中も含めた全コンテナ docker logs <id> コンテナのログを見る docker exec -it <id> bash 動作中コンテナに入る この5つで「動かす・状態を見る・ログを読む・中に入る」が全部できる。 Docker入門 / 技育アカデミア 22
  13. その他のコマンド コマンド 役割 docker pull <image> レジストリからイメージを取得 docker images ローカルのイメージ一覧

    docker stop <id> コンテナを停止 docker rm <id> コンテナを削除 docker rmi <image> イメージを削除 Docker入門 / 技育アカデミア 23
  14. 実例: nginxを起動してブラウザで見る # バックグラウンドでnginxを起動、ホストの8080をコンテナの80に繋ぐ $ docker run -d --name web

    -p 8080:80 nginx:1.27 # 動いているか確認 $ docker ps CONTAINER ID IMAGE STATUS PORTS NAMES abc123 nginx:1.27 Up 5 seconds 0.0.0.0:8080->80/tcp web # ブラウザで http://localhost:8080 を開く → Welcome to nginx! が見える # 停止して削除 $ docker stop web $ docker rm web -d = バックグラウンド / -p host:container = ポート公開 / --name = 名前付け Docker入門 / 技育アカデミア 24
  15. よく使うオプション オプション 意味 -d デタッチ(バックグラウンド実行) -it 対話的に使う( bash 等を起動したいとき) --rm

    終了時にコンテナを自動削除 --name <name> コンテナに名前を付ける -p HOST:CONTAINER ポートを公開する Docker入門 / 技育アカデミア 25
  16. その他のオプション オプション 意味 -v HOST:CONTAINER ボリュームをマウントする -e KEY=VALUE 環境変数を渡す --env-file

    <path> ファイルから環境変数を読み込む --network <net> 接続するネットワークを指定 --platform=linux/amd64 プラットフォームを明示 --restart=always 落ちたら自動再起動 Docker入門 / 技育アカデミア 26
  17. 環境変数の渡し方 3パターン # ① -e で個別に渡す $ docker run -e

    DB_HOST=db myapp # ② --env-file でファイルから読み込む $ docker run --env-file .env myapp # ③ ホストの環境変数を透過させる(値を書かない) $ docker run -e DB_HOST myapp 機密情報を -e で直書きすると docker inspect で見えてしまう .env は .gitignore に必ず追加(コミット事故が頻発) Docker入門 / 技育アカデミア 27
  18. docker exec でコンテナの中に入る # bashを起動して対話的に入る $ docker exec -it <container>

    bash # 中で1コマンドだけ実行する $ docker exec <container> ls /app # alpineコンテナには bash が無いので sh を使う $ docker exec -it alpine-container sh デバッグの基本コマンド。 「コンテナ起動してるのに繋がらない…」のとき、まずこれ で中を確認する。 Docker入門 / 技育アカデミア 28
  19. Dockerfile 主要命令 命令 意味 FROM ベースイメージを指定(必ず最初) WORKDIR 以降の作業ディレクトリ COPY ホストのファイルをイメージにコピー

    RUN ビルド時にコマンドを実行(レイヤを増やす) CMD コンテナ起動時に実行するデフォルトコマンド 最初はこの5つから。残りは必要になったら覚えればOK。 Docker入門 / 技育アカデミア 30
  20. その他の命令 命令 意味 ENV コンテナ実行時にも残る環境変数 ARG ビルド時だけ使える引数 EXPOSE 公開するポートを宣言(ドキュメント目的) ENTRYPOINT

    コンテナの「中身そのもの」になるコマンド USER 実行ユーザーを切り替える(本番では必須) HEALTHCHECK 死活監視コマンド Docker入門 / 技育アカデミア 31
  21. 実例: Node.jsアプリのDockerfile FROM node:20-alpine WORKDIR /app # 依存ファイルだけ先にコピー(キャッシュ効かせるため) COPY package.json

    package-lock.json ./ RUN npm ci # 残りのソースコードをコピー COPY . . EXPOSE 3000 CMD ["node", "server.js"] Docker入門 / 技育アカデミア 32
  22. ビルドして動かす # Dockerfileのあるディレクトリで実行 $ docker build -t myapp:1.0 . #

    -t : タグ付け(imageName:tag) # . : ビルドコンテキスト(このディレクトリ以下をDockerに渡す) # 出来上がったイメージから起動 $ docker run -d -p 3000:3000 --name app myapp:1.0 # ログを追いかけて確認 $ docker logs -f app docker build の最後の . を忘れがち( 「カレントディレクトリをビルドコンテ キストに渡す」の意味) Docker入門 / 技育アカデミア 33
  23. CMD と ENTRYPOINT の違い 設定 役割 CMD デフォルトで実行するコマンド。 docker run

    image arg で簡単に 上書き ENTRYPOINT コンテナの本体プロセス。 docker run image arg の arg は ENTRYPOINT の引数として渡る 3つの使い分けパターンを次のスライドで見ます。 Docker入門 / 技育アカデミア 34
  24. CMD と ENTRYPOINT — 3つのパターン # パターン1: CMDだけ → 「デフォルト動作」を提供

    CMD ["node", "server.js"] # パターン2: ENTRYPOINTだけ → 「このイメージは常にこのコマンド」 ENTRYPOINT ["python", "main.py"] # パターン3: 両方 → 「ENTRYPOINTがツール本体、CMDがデフォルト引数」 ENTRYPOINT ["python", "main.py"] CMD ["--help"] パターン3の動作: docker run myapp → python main.py --help docker run myapp arg1 arg2 → python main.py arg1 arg2 Docker入門 / 技育アカデミア 35
  25. COPY と ADD の違い 命令 用途 COPY ホストのファイルをコピー(基本これ) ADD COPY

    + URLダウンロード や tar展開(副作用多い、避ける) COPY src/ /app/src/ # 基本はこれ ADD https://example.com/file.tar.gz /tmp/ # ADD のURL機能 ADD archive.tar.gz /app/ # ADD のtar展開機能 公式も「COPYはADDより明示的で予測可能なので、こちらを使え」と推奨。 Docker入門 / 技育アカデミア 36
  26. ARG と ENV の違い 命令 いつ使われるか コンテナに残るか ARG ビルド時のみ 残らない

    ENV ビルド時 + 実行時 残る ARG VERSION=1.0 # --build-arg VERSION=2.0 で上書き可能 ENV APP_VERSION=${VERSION} # コンテナ実行時にも残る シークレットは ARG でも ENV でも渡さない。 docker history でレイヤに残るの で、BuildKit の --mount=type=secret を使う。 Docker入門 / 技育アカデミア 37
  27. マルチステージビルド 「ビルド時に必要なもの」を最終イメージに残さないテクニック。 # ステージ1: ビルド FROM golang:1.22-alpine AS builder WORKDIR

    /src COPY . . RUN CGO_ENABLED=0 go build -o /out/app . # ステージ2: 実行(ビルドツールを含まない) FROM alpine:3.20 COPY --from=builder /out/app /app CMD ["/app"] 最終イメージから Go本体・ソースが消える → 数百MB → 数MB に。 Docker入門 / 技育アカデミア 38
  28. キャッシュ判定のルール 命令 キャッシュ判定の方法 RUN / ENV / WORKDIR 等 Dockerfileに書かれた文字列が前回と同じか

    COPY / ADD コピー対象ファイルの内容(チェックサム)が同じか 上から順に判定し、1つでも「変更あり」と判定されると、それ以降は全部キャッ シュ破棄 だから「変更されやすいもの」ほど Dockerfileの後ろに書く のが鉄則 Docker入門 / 技育アカデミア 41
  29. キャッシュを効かせる書き方 — 悪い例 FROM node:20-alpine WORKDIR /app # ソース全部を先にコピー COPY

    . . # 依存インストール RUN npm ci CMD ["node", "server.js"] 問題: ソースを1行変えただけで npm ci がやり直しになる。 毎ビルド数分待たされる地獄。 Docker入門 / 技育アカデミア 42
  30. キャッシュを効かせる書き方 — 良い例 FROM node:20-alpine WORKDIR /app # 依存ファイルだけ先にコピー COPY

    package.json package-lock.json ./ RUN npm ci # ソースは最後にコピー COPY . . CMD ["node", "server.js"] 効果: package.json が変わらない限り npm ci のキャッシュが効く。 この順番がキャッシュを活かすコツ。 Docker入門 / 技育アカデミア 43
  31. キャッシュのヒット/ミスを判定する流れ 命令を見てハッシュを計算 │ ▼ 同じハッシュのレイヤが ── Yes ──► キャッシュ命中(既存レイヤを再利用) ローカルにある?

    │ No ▼ 新しいレイヤを生成 ──► これ以降の命令は **全部キャッシュ無効** つまり、 「上の方の命令ほどキャッシュの恩恵が大きい」 。 だから「変更頻度の低いもの → 高いもの」の順に Dockerfile を書く。 Docker入門 / 技育アカデミア 44
  32. .dockerignore でビルドコンテキストを軽くする .gitignore の Docker 版。 docker build 時にカレントディレクトリ以下がまるごと Docker

    daemonに送られるので、不要ファイルを除外すると 転送が速くなる & COPY . . でキャッシュが壊れる事故を防げる。 # .dockerignore の例 node_modules .git .env *.log dist/ .DS_Store Docker入門 / 技育アカデミア 45
  33. RUN は && でまとめる # NG: レイヤが増え、apt-get update のキャッシュが古くなる RUN

    apt-get update RUN apt-get install -y curl # OK: 同じRUNにまとめる RUN apt-get update && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* レイヤ数を減らし、 update と install の整合性も保てる。 Docker入門 / 技育アカデミア 46
  34. BuildKit のキャッシュマウント # syntax=docker/dockerfile:1.7 FROM node:20-alpine COPY package*.json ./ RUN

    --mount=type=cache,target=/root/.npm npm ci /root/.npm を Docker が管理する永続キャッシュとして保持し、 --no-cache でビル ドし直してもダウンロード済みパッケージは再利用される。 npm 以外: pip → /root/.cache/pip 、Go → /root/.cache/go-build 、apt → /var/cache/apt 。 Docker入門 / 技育アカデミア 48
  35. イメージサイズ削減のコツ 1. ベースイメージを slim / alpine に変える 2. マルチステージビルドでビルド用と実行用を分ける 3.

    RUN を && でまとめる、最後に rm -rf で削除物を片付ける 4. .dockerignore で node_modules / .git を必ず除外 5. パッケージマネージャの キャッシュを消す(apt: /var/lib/apt/lists/* ) Docker入門 / 技育アカデミア 49
  36. 2種類のマウント バインドマウント docker run -v $(pwd):/app myapp ホストのディレクトリをそのままマ ウント ホストの変更がコンテナに即反映

    開発時のソースコード共有に最適 名前付きボリューム docker run -v pgdata:/var/lib/postgresql/data postgres Docker が管理する領域にマウント ホストのファイルシステムからは見 えない DBデータの永続化に最適(公式推 奨) Docker入門 / 技育アカデミア 53
  37. docker volume コマンド $ docker volume create mydata # 作成

    $ docker volume ls # 一覧 $ docker volume inspect mydata # 詳細(ホスト上のパスや作成日時など) $ docker volume rm mydata # 削除(中身も消える!) $ docker volume prune # 使われてないボリュームを一括削除 Docker入門 / 技育アカデミア 54
  38. ユーザー定義ネットワークの強み コンテナ名でDNS解決できる。 $ docker network create mynet $ docker run

    -d --name db --network mynet postgres:15.4 $ docker run -d --name app --network mynet myapp $ docker exec app curl http://db:5432 # ← コンテナ名で繋がる Docker の埋め込みDNSサーバ ( 127.0.0.11 ) がコンテナ名→IPを解決してくれる。デ フォルトのbridgeでは名前解決できないので、必ず network create するか Compose を使う。 Docker入門 / 技育アカデミア 55
  39. ポートマッピング — 外の世界とつなぐ docker run -d -p 8080:80 nginx #

    -p <ホスト側>:<コンテナ側> ホスト側で別ポートにぶつかる心配があるなら左を変える -p 127.0.0.1:8080:80 で外部公開を防ぐバインドも可能 Docker入門 / 技育アカデミア 56
  40. 実際のアプリは「複数コンテナ」で動く これを docker run だけで管理すると: 長い docker run ... を何個も叩く必要がある

    起動順序を人間が管理する( sleep 10 で雑に待つ世界) ネットワーク作成も自前 停止も docker stop を全部に対して… チーム共有が現実的に不可能になる。 Docker入門 / 技育アカデミア 58
  41. Docker Composeなら1コマンド $ docker compose up -d これだけで、以下を全自動で実行してくれる: ネットワーク・ボリュームの作成 イメージの

    build / pull depends_on + healthcheck による起動順序の制御 全コンテナの起動 Docker入門 / 技育アカデミア 59
  42. Docker Compose の公式ポジション Compose は、マルチコンテナアプリケーションを1つのYAMLファイルで定義・実 行するためのツール 主な利点: マルチコンテナを1ファイルで定義 → チームで共有可能

    変更のないサービスは既存コンテナを再利用(高速) 変数機能で環境差を吸収 compose.yaml を Git に置けば、 git clone → docker compose up -d で環境が完成 する。 Docker入門 / 技育アカデミア 60
  43. compose.yaml のトップレベル構造 name: myapp # プロジェクト名 (省略可) services: # ←

    コンテナ群の定義(必須) app: ... volumes: # ← 名前付きボリュームの宣言 pgdata: networks: # ← ネットワーク(任意) secrets: # ← シークレット(任意) configs: # ← 設定ファイル(任意) 多くのプロジェクトでは services + volumes だけで十分。 他のキーは「そういうのもある」程度で覚えておけばOK。 Docker入門 / 技育アカデミア 61
  44. compose.yaml のサンプル services: app: build: . ports: ["8080:3000"] depends_on: db:

    { condition: service_healthy } db: image: postgres:15.4 environment: { POSTGRES_PASSWORD: password } volumes: ["pgdata:/var/lib/postgresql/data"] healthcheck: { test: pg_isready, interval: 1s, retries: 60 } volumes: pgdata: Webアプリ + DB の典型構成。 Docker入門 / 技育アカデミア 62
  45. サービスとイメージの指定 services 動かしたいコンテナを「サービス」とし て定義。 services: app: ... db: ... サービス名は

    コンテナ間通信のホスト名 にも使える。 build / image build: — Dockerfileからビルドす る image: — レジストリの既存イメー ジを使う services: app: build: . db: image: postgres:15.4 Docker入門 / 技育アカデミア 63
  46. ポート・環境変数・ボリュームの設定 ports / environment ports: - "8080:3000" # host:container environment:

    NODE_ENV: development DATABASE_URL: postgres://...@db:5432/app DBへの接続は サービス名 db をホ スト名として使える 秘密情報は .env + env_file: に 逃がす volumes volumes: - .:/app # bind mount - pgdata:/var/lib/... # named volume トップレベルでも宣言が必要: volumes: pgdata: Docker入門 / 技育アカデミア 64
  47. build: の詳細 services: app: build: context: . # Dockerfileの探索起点 dockerfile:

    ./docker/Dockerfile # Dockerfileの場所 args: { NODE_VERSION: "20" } # ビルド時の引数 target: production # マルチステージのどこまで作るか platforms: [linux/amd64, linux/arm64] 短く書くなら build: . だけでもOK。 Docker入門 / 技育アカデミア 65
  48. 起動順序の制御 — depends_on + healthcheck services: app: depends_on: db: condition:

    service_healthy # ← DB が healthy になるまで待つ db: image: postgres:15.4 healthcheck: test: pg_isready -U postgres interval: 1s retries: 60 depends_on だけでは「起動した」までしか待たない。 「準備完了」を待つには condition: service_healthy をセットで使う。 Docker入門 / 技育アカデミア 66
  49. .env ファイルと変数展開 # .env (プロジェクトルートに置く) POSTGRES_PASSWORD=supersecret APP_PORT=8080 # compose.yaml services:

    app: ports: - "${APP_PORT:-3000}:3000" db: environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} .env の値が ${VAR} として自動展開される。 Docker入門 / 技育アカデミア 67
  50. .env のハマりどころ .env は .gitignore に必ず追加(コミット事故が頻発) ${VAR:-default} で「未定義時のデフォルト値」を指定できる 展開後のyamlを確認したいときは →

    docker compose config .env の置き場所は compose.yaml と同じディレクトリが基本 # 変数が反映されないときに最初に叩くコマンド $ docker compose config # → 展開後の最終yamlが画面に出る # 値が空・古い値ならファイル位置や名前を確認する Docker入門 / 技育アカデミア 68
  51. 日常で使う Compose コマンド # 起動(バックグラウンド) docker compose up -d #

    イメージを再ビルドしてから起動 docker compose up -d --build # 停止 & 削除(ボリュームは残る) docker compose down # データもまっさらに削除(DBデータも消える!) docker compose down -v Docker入門 / 技育アカデミア 69
  52. 状態確認・デバッグ用の Compose コマンド # 状態を確認 docker compose ps # ログをリアルタイムで見る

    docker compose logs -f app # コンテナに入る docker compose exec app bash # 設定の展開後を確認(デバッグ最強) docker compose config docker compose config は .env を展開した最終yamlを表示する。 変数が反映されないときの確認に使える。 Docker入門 / 技育アカデミア 70
  53. トラブル時はこの順で見る 1. docker compose ps で全コンテナの STATUS を確認 2. docker

    compose logs <service> でログを読む(ここが一番大事) 3. ログから原因がわかったら、設定を直して up -d --build 4. どうしても直らない最終手段: down -v && up -d --build (データもキャッシュもまっさらにしてやり直す) Docker入門 / 技育アカデミア 71
  54. compose.yaml 読解の例(抜粋) services: app: build: { context: ., dockerfile: ./docker/Dockerfile

    } ports: ["8080:8000"] volumes: - .:/app # ① ソースを bind mount - app_cache:/root/.cache # ② キャッシュを named volume depends_on: db: { condition: service_healthy } db: image: postgres:15.4 healthcheck: { test: pg_isready -U postgres, interval: 1s, retries: 60 } volumes: ["pgdata:/var/lib/postgresql/data"] volumes: { pgdata: {}, app_cache: {} } Docker入門 / 技育アカデミア 72
  55. 補足: ファイル名の歴史 ファイル名 状況 compose.yaml 現在の推奨(Compose V2 以降) docker-compose.yml 旧名(今も動作する)

    compose.yml これも動作する 旧: docker-compose up (Compose V1, スタンドアロン) 新: docker compose up (Compose V2, Dockerのサブコマンド) 新規プロジェクトは compose.yaml + docker compose 形式で書きましょう。 Docker入門 / 技育アカデミア 73
  56. 今日の振り返り 1. 仮想化とコンテナの位置づけ 2. 3要素:Dockerfile / Image / Container 3.

    CLIの基本とトラブルシュート 4. Dockerfile とマルチステージ 5. ビルドキャッシュの仕組み 6. Volume と Network 7. Docker Compose の使い方 Docker入門 / 技育アカデミア 75
  57. 学ぶときのコツ 「動かす → 中を覗く → 壊して直す」を繰り返す。 公式イメージを docker run して中に

    exec -it bash で入ってみる Dockerfileを写経して docker build してみる わざとtypoして、エラーメッセージを読む練習をする OSSのリポジトリで compose.yaml を読み漁る Docker入門 / 技育アカデミア 77