はじめに
RUNTEQ Advent Calendar 2025 の25日目を担当します、takaと申します。
現在は未経験からエンジニア転職をするために学習をしています。
今回はタイトルのとおりDockerについて、主にDockerfileとdocker-compose.ymlを軸に説明を進めた記事となります。
Docker初学者が書いた記事となりますので、誤り等がある可能性がございますがその際はお手数ですがコメント等にてご教授いただけますと幸いです。
環境
ホストPC = ローカルPCマシンと仮定します
Docker Desktopを使用します
macOSを使用します
本記事の目的
Dockerに対する解像度を少し上げる
数ヶ月前の私がDockerと聞いて連想するのは、以下のとおりでした。
コンテナ、クジラ、docker compose up、Dockerfileなど。
もちろんDockerfileがなんなのかもよく分かっていませんでした。
本記事を通して、
Dockerを使う上で柱となるDockerfile、docker-compose.ymlを使って、それぞれがどんな役割となっているかを何となく掴んで頂くことで、色々な形に応用できる元となれば幸いです。
前提
Dockerは便利なものであるが故どのような形にもなるため、その実体のようなものが掴みづらいものかと考えます。本記事で例に挙げておりますものは、Docker運用の一部となります。
そのため、Dockerの側面を捉えるために表現が偏っている場合がございます。
※より詳細かつ質の高いものは、RUNTEQカリキュラムにございます。
結論
先に結論を書きますが、
Dockerは、アプリを動かすための「実行環境ごとパッケージ化」できる仕組みです。
Dockerfileでイメージの中身(OSやライブラリ、アプリ)を定義し、docker-compose.ymlで複数コンテナの起動方法や接続関係(ポート、ネットワークなど)をまとめて管理できます。
これらの噛み砕いた説明を下記に沿って、行なっていきます。
Dockerの強みとは
「どんな環境でも同じ実行環境を再現できる」ことです。
※実行環境とは、アプリが実際に動いている「場所+中身のセット」のことです。
例えば、ローカル開発環境でアプリの動作が確認できたもの一式(秘密情報以外)を本番環境にそのまま持っていけるといった使い方ができます。
開発者のPCでもデプロイ先の本番サーバでも、Dockerイメージを使えば全く同じ環境がすぐに立ち上がり、環境差異によるトラブルをなくせます。
この仕組みを支えているのが、次に説明する「コンテナ」となります。
コンテナとは
Dockerfile から docker build によって作られるのが「イメージ」です。
そのイメージを元に実体として起動する箱が「コンテナ」です。
この中に使いたいものだけを入れて各実行環境で使うことができます。
また、docker-compose.ymlがある場合は、docker compose buildになります。
Dockerfileを元に一度作られたイメージは、ローカル環境・テスト環境・本番環境など、さまざまな場所で同じコンテナを作成して使い回すことが可能です。
開発環境・テスト環境・本番環境といった区分は、それぞれの用途ごとに用意された実行環境であり、開発用コンテナとは別に、テスト用に必要なものだけを含んだコンテナなどを指定して作成することもできます。
コンテナ作成までの流れ
コンテナを作成・運用する上で必要なファイルが2つあります。
それはDockerfileとdocker-compose.ymlです。
この2つの目的と相互関係を理解することが、Docker理解への重要なポイントとなりそうです。
それぞれの役割
🗂️Dockerfile
=コンテナの元となる「イメージを生成するため」の設計図です。
一度作ったイメージは、ターミナルでdocker imagesと入力することや、Docker Desktopのimagesで確認することができます。
簡単なイメージを作ってみる。
# Dockerfile
FROM ruby:3.2
この1行だけで、buildコマンドをするとRubyが入ったイメージを作ることができます。
FROMの部分には、
使いたいイメージを記載します。
今回の例では、Ruby公式イメージを使用しています。(公式イメージについて後述します)
つまり、このDockerfileは、
「Ruby3.2系の公式イメージを使って、新しいイメージを作る」というものになります。
このDockerfileから作られたイメージは、Dockerfileに公式イメージしか書いていないので、それ以外は何も入っていないものになります。
ですので、後はDockerfileの書き方に沿って、ローカル開発環境のディレクトリをコピーしたりさらに必要な公式イメージを追加するといったオリジナルのイメージを作るといった感じになります。
また、FROM には公式イメージだけでなく、
〇〇開発専用といったオリジナルのイメージを指定することも可能です。
※公式イメージとは、「Docker Hubのレジストリ」という場所にRubyやDBであるPostgre SQLなどが「既にインストールされたイメージ」として保管されており、レジストリから使いたい公式イメージを自由に選ぶことができます。
(※AIで指示すれば、使いたい公式イメージを記載してくれます)
参考:Rubyのgem機能を使って、Ruby on Railsを使いたい時にその公式イメージを記載しなくても、Dockerfile内でgemfileをbundle installして使えるようにすることも可能です。もちろん、rubocop君も連れてこれます。
Dockerfileに追記して、もう少しオリジナリティを加える
作業のゴールは、ローカルで動作確認ができたRuby on Railsを使用したアプリをDocker化することと仮定します。
以下の内容で簡単な独自イメージを作ります。
各項目は、上から順に実行されていきます。
# Dockerfile
FROM ruby:3.2
WORKDIR /docker_app
ENV BUNDLE_PATH=/vendor/bundle \
BUNDLE_BIN=/bundle/bin \
BUNDLE_JOBS=4 \
BUNDLE_RETRY=3
ENV PATH="$BUNDLE_BIN:$PATH"
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY . .
Dockerfileの内容について、
・イメージ内にディレクトリを作る
・gemfileをbundle installした際の保管場所を指定
・ローカルにあるgemfileとgemfile.lockをイメージ内のディレクトリにコピー
・ローカルのアプリディレクトリを、イメージ内のディレクトリにコピー
といった内容です。
これで起動したコンテナの中にアプリディレクトリがある状態となります。
=ローカルで作ったMVC設計などがそのまま一式、イメージに入る。
WORKDIR /docker_app # イメージの中にdocker_appというディレクトリを作成し、
# カレントディレクトリとして扱い、Dockerfile内の様々なコマンドの
# 対象とします。
# Dockerfile内の COPYやRUNなどの命令の対象となるという意味です。
#bundle installのインストール先を固定。
ENV BUNDLE_PATH=/vendor/bundle \
BUNDLE_BIN=/bundle/bin \
BUNDLE_JOBS=4 \
BUNDLE_RETRY=3
ENV PATH="$BUNDLE_BIN:$PATH"
COPY Gemfile Gemfile.lock ./ # ローカルのGemfileとGemfile.lockをdocker_appにコピー
# する。 .は、カレントディレクトリを指します。
RUN bundle install #先ほどGemfileをコピーしたので、インストール。
COPY . . # 自分のPCにあるアプリディレクトリをイメージの中のdocker_appディレクトリにコピー
# する。
#これでローカルPCにあるアプリディレクトリと同じものがイメージ内に置けます。
#1つ目の.は、docker-compose.ymlに記載されたビルドコンテキストを指します。
#(ビルドコンテキストについては、docker-compose.ymlに書いています。)
#2つ目の.は、前述した作業ディレクトリ(docker_app)を指します。
#つまり、COPY . .は、ビルドコンテキストをdocker_appにコピーするという意味です。
#今回は、ビルドコンテキストをローカルのアプリディレクトリと指定しているので、
#「自分のPCにあるアプリディレクトリをイメージの中のdocker_appディレクトリに
#コピーする」という挙動になります。
Gemfile と Gemfile.lock をコピーしてからアプリディレクトリ全体をコピーしているので、
「2回コピーしていませんか?」となりますが、これは意図的に2回コピーしています。
一度 COPY Gemfile Gemfile.lock ./ と書いておくことで、build 後にその結果がキャッシュとして保存されます。
そのため、再 build 時にはそのキャッシュが使われ、Gemfile に変更がなければ、
Gemfile に書かれた gem を最初から全てインストールし直す必要がありません。
= build 時間の短縮につながります。
🗂️docker-compose.yml
=作られたイメージを元に
コンテナをどのような設定で起動・運用するかを定義するファイルです。
先ほど、ローカルにあるアプリディレクトリをイメージに入れる設計図を書きました。
次は、そのイメージで作られるコンテナをどのように起動・運用するかをdocker-compose.ymlで設定します。
docker-compose.ymlは、YAML形式で書かれたファイルであり、データを「キー: 値」で書くことができます。
# docker-compose.yml
services:
web:
build: .
ports:
- "3000:1000"
command: bash -lc "bin/rails s -p 1000 -b 0.0.0.0" #railsサーバーを起動
書いている内容は、
・Dockerfileで出てきた「ビルドコンテキスト」は、build:の部分で指定していること
・ブラウザとコンテナ内のアプリサーバーを繋げる
・コンテナの中で、bin/rails sを実行し、サーバーを起動させる。portは1000とする
といったものになります
ビルドコンテキストについて
build: . #.はdocker-compose.ymlを置いているディレクトリを指す。ローカルのアプリディ
レクトリをlocal_appとした場合、local_app直下にdocker-compose.ymlを置く
ことで、local_appが丸ごと「ビルドコンテキスト」になります。
先ほどのDockerfileに書いたCOPYの1つ目の.は、docker-compose.ymlを置いて
いる場所をコピー元としていたということになります。
ビルドコンテキストの部分でも分かるように、Dockerfileの内容とdocker-compose.ymlは、密接な関係にあります。
ports:
- "3000:1000"
#3000は、ホストPCのport番号
#1000は、コンテナのport番号(-p 1000 -b 0.0.0.0"の部分)
#ブラウザとコンテナ内のアプリサーバーを繋ぐ役割を果たしています。
前提として、DockerはLinux環境でしか動作しません。
ローカルPCの場合、Docker Desktopを使うことで作成したイメージや起動したコンテナは、Docker Desktopが仮想化したLinux環境(Linux VM)で動作しています。
また、このLinuxVMは手元PCのハードウェア上で動いていますが、手元PCのOSとは独立したものとして扱われています。
ブラウザと手元PC内のコンテナは直接やり取りはできないので、
ブラウザでコンテナ内のアプリサーバーとやり取りするには、まず手元PCがブラウザのリクエストなどを受け取って、それをコンテナに転送する作業が必要になります。
このため、両者を繋ぐ設定(ポート公開など)が必要になります。
(参考:homebrewやWSL2でLinuxOSを導入し、そこにDocker engineを置くことも可能なようです。)
command: bash -lc "bin/rails s -p 1000 -b 0.0.0.0"
#railsサーバーを起動し、portを1000とするという設定。
ファイル作成は以上になります
繰り返しになりますが、
Dockerfileは「コンテナの中身をどう作るか」 を決める設計書で、
docker-compose.yml は 「そのコンテナをどの設定で動かすか」 を決める運用書です。
このように両ファイルは密接な関係があります。
build後、コンテナを起動すると
ローカルでアプリの正常動作が確認できている状態で、実際に上記のファイルを使用して
docker compose build+docker compose upを実行した後に、localhost:3000をブラウザでアクセスすると、コンテナの中にあるアプリが表示されます。
実際のところ
これらのファイルは、"とりあえず動くもの"であり、運用するには更なる細かな設定が必要ですが各ファイルにどんなことを書いているのかをざっくりと捉えて頂くことで、Dockerに対する解像度が少しでも上がれば幸いです。
まとめ
・Dockerfileは、コンテナの元となるイメージを作成するための設計図
・docker-compose.ymlは、イメージを元にコンテナをどのように運用するかの設定書
・2つは密接な関係がある
・各ファイルお決まりの書き方にそって、オリジナリティを加えることができる
使用するインフラ環境や設計によって、自由に形を変えることができるためDockerは、便利ではありますが掴みづらい部分があるのかと思います。
その際に、上記4点をベースに考えることで何となく解像度が上がれば幸いです。