「Docker」を使ってアプリケーションをパッケージングしよう

2025年7月15日(火)
石本 達也
第10回の今回は、「Docker」を使ってアプリケーションをパッケージングする方法について、基本からステップバイステップで解説します。

はじめに

近年、ソフトウェア開発の現場では「コンテナ」を活用した開発スタイルが主流になりつつあります。開発現場では「手元ではアプリケーションは動くのに、本番環境では動かない」といった環境依存の問題が頻発していましたが、コンテナ技術の普及により、こうした課題を根本から解決しようという動きが加速しています。

コンテナを使うことで、アプリケーションとその実行に必要なすべてのライブラリや設定を1つの「箱」にまとめ、どこでも同じように動作させることが可能になります。これにより、開発環境・テスト環境・本番環境のすべてで一貫性のある動作を保証し、デプロイの効率化や自動化も実現できます。

本記事では「Docker」を使ってアプリケーションをコンテナへパッケージングする方法を解説します。この記事を読み終えるころには、再現性の高い実行環境を作り上げ、チーム開発や運用へ活かせるようになることを目指します。

「Dockerとは何か」という基本から始め、実際に簡単なWebアプリケーションをDocker化する実践例、そしてよくある改善ポイントやベストプラクティスまで、ステップバイステップで解説します。

Dockerとは何か

アプリケーションの開発や運用をしていると「開発環境では動くのに本番環境では動かない」といった問題に直面することがあります。これは多くの場合、OSやライブラリのバージョン違い、設定の差異といった環境依存によるものです。Dockerは、こうした課題を解決するために登場しました。ここでは、Dockerの基本を押さえ、なぜこれが注目されているのかを理解しましょう。

コンテナと仮想マシンの違い

まず、Dockerを理解するために「コンテナ」と「仮想マシン(VM)」の違いを整理します。

特徴 コンテナ(Docker) 仮想マシン(VM)
実行環境 カーネルを共有 ゲストOSを含む完全な環境
起動時間 数秒程度 数分かかることが多い
リソース消費 軽量 重い(OSごとにメモリ/CPUを消費)
移植性 3.369 比較的低い

仮想マシンはハイパーバイザー上でOS丸ごと仮想化します。つまり、従来のOS同様、ブートプロセスやOSを動かすためのプロセスの起動などが実行されます。

一方で、コンテナはホストOSとカーネルを共有し、あるプロセスのみをホストOSから分離します。これにより、コンテナはブートプロセスやOSを動かすためのプロセスの起動などをスキップし、あるプロセスのみを起動するため、軽量かつ高速に動作します。

Dockerの基本概念

Dockerは、コンテナ技術を活用するためのプラットフォームです。以下の主要な概念があります。

  • イメージ(Image)
    アプリケーションとその依存関係、設定をまとめたテンプレート。何度でも同じ環境を作り出せる
  • コンテナ(Container)
    イメージを実際に実行したもの。一時的な実行環境であり、必要に応じて作成・削除が簡単にできる
  • Dockerfile
    イメージを作成するためのレシピファイル。どのOSベースにするか、どのパッケージをインストールするか、どのコマンドを実行するかなどを定義する
  • レジストリ(Registry)
    イメージを共有・管理するためのサービス。代表例はDocker Hubで、そこから既存のイメージを取得したり、自分のイメージを公開できる

DevOpsにおけるDockerの役割

現代のソフトウェア開発では、開発からテスト、本番運用までの一貫した環境を整えることが重要です。ここでDockerが大きな力を発揮します。

  • 開発者はローカル環境に依存せず、誰でも同じ環境でアプリを動かせる
  • テスターは本番に近い環境で検証が可能
  • 運用担当者は環境構築の差異によるトラブルを減らせる

このように、Dockerは「一度作れば、どこでも同じように動く」を実現し、DevOpsやCI/CD(継続的インテグレーション/継続的デリバリー)の基盤技術として広く使われています。

パッケージングの全体像をつかもう

アプリケーションをDockerで「包む」とは

「アプリケーションをDockerでパッケージングする」とは、ただソースコードをまとめるだけの作業ではありません。アプリケーションが動作するために必要なすべての要素、ライブラリ、設定ファイル、OSレベルの依存関係までを1つにまとめ、どの環境でも同じように動作する“実行可能な箱”を作ることです。

この“箱”の正体がDockerイメージです。そして、このイメージから実際に動作するプロセスとして生成されるのがDockerコンテナです。イメージとコンテナの関係は「レシピ」と「料理」に例えると分かりやすいでしょう。レシピ(イメージ)を元に、必要な時に何度でも料理(コンテナ)を作り出すことができます。

概念図: アプリケーションパッケージングの全体像

このようにして作成されたDockerイメージを用いれば、開発者のPC、テストサーバー、クラウド上の本番環境など、あらゆる環境で同じ挙動が保証されます。

依存関係の明示と再現性の確保

現代のアプリケーションは、多くの外部ライブラリやミドルウェアに依存しています。Pythonならrequirements.txt、Node.jsならpackage.jsonといった依存関係の一覧がありますが、これだけではOSやシステムパッケージの違いまでは管理できません。

そこで活躍するのが「Dockerfile」です。Dockerfileには、次のような情報をすべてコードとして記述します。

  • どのOSイメージをベースにするか
  • どのライブラリやツールをインストールするか
  • アプリケーションの配置場所や起動方法

このようにして作成されたDockerfileからイメージをビルドすることで、誰がどの環境でビルドしても同じ実行環境が再現されます。

開発から本番まで一貫性のある環境を提供

「開発環境では動くのに、本番では動かない」という問題は、開発と本番で使用している環境が異なることに起因します。例えば、ライブラリのバージョンや設定ファイルの差異がエラーの引き金になります。

Dockerを使えば開発・テスト・本番のすべてで同じDockerイメージを共有できるため、このような環境差異によるトラブルを未然に防げます。さらにデプロイも簡単です。サーバーにイメージを配置しコンテナを起動するだけで、複雑な環境構築は不要になります。

なぜDockerが最適なのか

これまでも仮想マシン(VM)を使えば似たことができましたが、Dockerは「軽量で高速」という大きな利点があります。VMはOSごと仮想化するため起動に時間がかかり、リソースも多く消費します。一方、DockerはホストOSのカーネルを共有しつつ、必要な部分だけをコンテナ化するため、起動は数秒、リソース消費も最小限です。

この軽快さが開発現場でのスピード感やクラウド上でのスケーラビリティに直結し、Dockerが広く支持される理由になっています。

実践:簡単なWebアプリケーションをDocker化する

ここからは、実際に簡単なWebアプリケーションをDockerでパッケージングしてみましょう。「理屈は分かったけど、具体的には何をすれば良いの」という疑問を持つ方に向けて、ステップバイステップで解説します。

対象アプリケーション:Python Flask

今回Docker化するのは、Pythonの軽量Webフレームワーク「Flask」を使ったシンプルなWebアプリです。内容はとても簡単で「Hello, Docker!」と表示するだけのものです。

まず、以下のようなディレクトリ構成を用意します。

hello-docker/
├── app.py
├── requirements.txt
└── Dockerfile

app.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, Docker!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

requirements.txt

flask

requirements.txtは、Flaskがこのアプリに必要な依存関係であることを明示しています。

Dockerfileの作成

次に、このアプリケーションをコンテナ化するためのDockerfileを作成します。

# 1. ベースイメージを指定
FROM python:3.12-slim

# 2. 作業ディレクトリを設定
WORKDIR /app

# 3. 依存関係ファイルをコピー
COPY requirements.txt .

# 4. 依存関係をインストール
RUN pip install --no-cache-dir -r requirements.txt

# 5. アプリケーションコードをコピー
COPY . .

# 6. コンテナ起動時のコマンド
CMD ["python", "app.py"]

【ここがポイント】

  • ベースイメージpython:3.12-slim。Pythonがあらかじめセットアップされたコンテナイメージで、容量もコンパクト
  • WORKDIRで作業ディレクトリを設定し、以降のコマンドをその中で実行
  • 依存関係のインストールでは--no-cache-dirオプションを使うことで、ムダにイメージサイズが増えるのを防ぐ
  • 最後のCMDでアプリケーションの起動方法を指定

イメージのビルド

Dockerfileができたら、次はDockerイメージをビルドしてみましょう。

docker build -t hello-docker .

このコマンドで、カレントディレクトリにあるDockerfileをもとにhello-dockerという名前のイメージが作成されます。

$ docker images

REPOSITORY    TAG     IMAGE ID       CREATED          SIZE
hello-docker  latest  1f5e17663605   41 seconds ago   163MB
...

コンテナの起動と動作確認

続いて、ビルドしたイメージをもとにコンテナを起動します。

docker run -p 5000:5000 hello-docker

ここで、-p 5000:5000はホストマシンのポート5000をコンテナのポート5000に対応づけるオプションです。

また、5000:5000の部分はホスト側のポート(左側)とコンテナ側のポート(右側)を指定しています。これにより、ホストマシンのポート5000にアクセスすると、コンテナ内のアプリケーションにリクエストが転送されます。

起動後、ブラウザでhttp://localhost:5000にアクセスすると、以下のように表示されます。

Hello, Docker!

これで、FlaskアプリをDockerコンテナとして実行できたことになります。

このように、Dockerを使えばアプリケーションを簡単にパッケージ化し、どこでも同じ環境で動かすことができます。次は、このDockerfileをさらに効率的かつ安全に改善していく方法を見ていきましょう。

よくあるDockerfileの改善ポイント

Dockerfileを書いてアプリケーションをDocker化した後、最初は「動けばOK」と思いがちです。しかし、実際の開発・運用ではビルド時間の短縮やイメージサイズの削減、セキュリティ強化が重要になります。ここでは、よくあるDockerfileの改善ポイントを紹介します。

キャッシュを活かす構造にする

Dockerは各命令(RUNCOPYなど)の実行結果をレイヤーとしてキャッシュします。このキャッシュをうまく使えば再ビルド時に不要な処理をスキップでき、ビルド時間を大幅に短縮できます。

【なぜキャッシュが重要なのか】
コードの一部を修正しただけで、依存関係のインストールからやり直したという経験はありませんか。キャッシュを活かせば、このムダを減らせます。

【改善例】
依存関係のインストールとアプリケーションコードのコピーを分けるのがポイントです。

# 悪い例
COPY . /app
RUN pip install -r /app/requirements.txt

# 良い例
COPY requirements.txt /app/
RUN pip install -r /app/requirements.txt
COPY . /app

悪い例はアプリケーションコードと依存関係ファイルを一緒にコピーしているため、コードを変更するたびに依存関係のインストールもやり直されます。

良い例ではrequirements.txtを先にコピーして依存関係をインストールしています。これにより、アプリケーションコードを変更してもrequirements.txtに変更がなければ、pip installはキャッシュされます。

イメージサイズの最適化

Dockerイメージが大きいとビルド・プッシュ・プルの時間が増加しCI/CDの速度にも影響するため、軽量化は必須です。

【改善ポイント】

  • 軽量ベースイメージの利用
    例えば、python:3.12ではなくpython:3.12-slimを選ぶだけでサイズを大幅に削減できる
  • 不要ファイルの削除
    ビルド後に不要なキャッシュや一時ファイルは削除する
  • マルチステージビルド
    ビルド専用のコンテナと実行用のコンテナを分けて、実行に不要なツールを排除できる

【マルチステージビルド例(Pythonアプリ)】

# ビルド用ステージ(依存関係のインストール)
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# 実行用ステージ
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /root/.local /root/.local
COPY . .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]

これで開発ツールが最終イメージに含まれず、サイズが大幅に減ります。

セキュリティ面の考慮

Dockerイメージは「そのまま本番で動く」ため、セキュリティ対策も重要です。

【よくある対策】

  • 不要なパッケージを入れない
    ベースイメージに含まれる不要なツールを極力減らす
  • ユーザー権限の制限
    デフォルトはrootユーザーで実行されるが、本番では権限を絞ったユーザーを使うべき
    # ユーザー追加と切り替え
    RUN useradd -m appuser
    USER appuser
  • パッケージのアップデート
    セキュリティパッチの適用も忘れずに。例えば、Debian/Ubuntu系では以下のように記述する
    RUN apt-get update &&
        apt-get upgrade -y  --no-install-recommends && \
        apt-get clean && \
        rm -rf /var/lib/apt/lists/*

--no-install-recommendsオプションを使うことで不要なパッケージのインストールを防ぎ、イメージサイズも小さく保てます。また、/var/lib/apt/lists/*を削除することでキャッシュファイルも削除し、イメージサイズをさらに小さくします。

.dockerignoreを活用する

Dockerイメージに不要なファイル(.git、node_modules、ローカル設定ファイルなど)が含まれているとサイズが膨らみ、セキュリティ上のリスクにもなります。

.dockerignoreを設定すると、これらをビルドコンテキストから除外できます。

例:.dockerignore

.git
node_modules
*.log
.env

これらの改善を取り入れることでDockerイメージはより軽量でビルドも高速化され、本番環境でも安心して利用できるようになります。特にチーム開発やCI/CDでの運用を考えると、効率とセキュリティの両面で大きな効果があります。まずは小さな改善から試し、最適なDockerfileを目指してみましょう。

チーム開発・CI/CDとの連携を見据えて

Dockerを使ったアプリケーションのパッケージングは、個人開発だけでなくチーム開発や継続的インテグレーション/デリバリー(CI/CD)においても非常に強力です。ここでは、チームでの運用やCI/CDパイプラインにDockerを取り込む際に役立つポイントを紹介します。

.dockerignoreの活用

Dockerfileと同じディレクトリに配置する.dockerignoreは、Dockerビルド時に不要なファイルやディレクトリを除外するための設定ファイルです。Gitで言うところの.gitignoreと同じ考え方です。

例えば、以下のようなファイルはDockerイメージに含める必要がありません。

node_modules
.git
*.log
.env

これを除外しないと、ビルドコンテキストに大量の不要データが含まれ、ビルド時間の増加やイメージサイズの肥大化を引き起こします。.dockerignoreを使うことで、効率的かつ安全なイメージ作成が可能になります。

バージョン管理とタグ運用

Dockerイメージはビルドするたびに「タグ」を付けて管理します。例えば、以下のようにタグを付けることで、どのバージョンのイメージか一目で分かります。

myapp:1.0.0
myapp:20250704
myapp:latest
  • セマンティックバージョニング(1.0.0のような形式)を使うと、変更の影響範囲が把握しやすくなる
  • 日付ベースのタグはCI/CDパイプラインで自動生成するケースが多い
  • latestタグは便利だが常に最新のものを指すため、本番環境では意図しないバージョン変更を引き起こすリスクがある。本番では特定のバージョンを明示的に指定するのがベスト

チーム開発では、タグ付けルールをあらかじめ決めておくことが重要です。

CI/CDパイプラインでのDockerビルド

CI/CD環境(GitHub Actions、GitLab CI、Jenkinsなど)では、コードのプッシュやマージに合わせてDockerイメージを自動でビルド・テスト・デプロイする仕組みを構築できます。

例えば、GitHub Actionsでは以下のようなジョブを組みます。

  1. リポジトリのコードをチェックアウト
  2. Dockerイメージをビルド
  3. テストを実行
  4. イメージをコンテナレジストリ(Docker Hub、Amazon ECRなど)にプッシュ
  5. 本番・ステージング環境にデプロイ

この自動化により人手によるミスを減らし、コードがマージされた瞬間に新しいイメージが展開されるようになります。

トラブルシューティングとベストプラクティス

Dockerでアプリケーションをパッケージングしていると「思った通りに動かない」「開発環境では動くのに本番でエラーが出る」といったトラブルに直面することがあります。ここでは、よくあるエラーの例とその対処法、さらに本番運用を見据えたベストプラクティスを紹介します。

よくあるエラーと対処法

【ポートの衝突】
症状:docker runでコンテナ起動時に「port is already allocated」と表示される。
原因:ホスト側のポートが他のプロセスやコンテナで使用中。
対処法:
・使用中のポートを確認

lsof -i :	<ポート番号>
・別のポートでコンテナを起動
docker run -p 8081:80 myapp
・不要なコンテナを停止・削除
  docker ps
  docker stop <コンテナID>
  docker rm <コンテナID>

【依存解決の失敗】
症状:docker build時に「package not found」や「failed to fetch」といったエラーが出る。
原因:
・Dockerfile内のパッケージリポジトリが古い
・ネットワークの一時的な不調
対処法:
・リポジトリを更新するコマンドを追加

  RUN apt-get update && apt-get install -y <パッケージ名>
・ビルド時にキャッシュ無効化
docker build --no-cache -t myapp .

【アプリケーションがすぐ終了する】
症状:docker run後、コンテナが即座に終了。
原因:
・DockerfileのENTRYPOINT/CMDの指定ミス
・アプリの設定ファイル不足
対処法:
・ENTRYPOINT/CMDを確認
・ログで原因特定

docker logs <コンテナID>
・対話モードで起動し調査
docker run -it myapp /bin/bash

ベストプラクティス

【ログの活用】
アプリケーションは標準出力・標準エラーにログを出力するよう設計し、docker logsで確認しやすくします。本番では外部のログ管理システム(例:ELKスタック)を利用するのも有効です。

【ヘルスチェック設定】
DockerfileにHEALTHCHECKを追加し、アプリケーションの正常性を定期確認。

HEALTHCHECK CMD curl --fail http://localhost:8080/health || exit 1

【セキュリティ強化】
・イメージ内ではrootユーザーでなく専用ユーザーを作成して実行

  RUN useradd -m appuser
  USER appuser
・ベースイメージを定期的に更新し脆弱性を修正

【軽量イメージの利用】
Alpine Linuxベースなどの軽量イメージを選ぶとイメージサイズが減り、攻撃対象面も小さくなります。

FROM python:3.10-alpine

【マルチステージビルド】
ビルドと実行環境を分離し、不要ファイルを含めずにコンパクトで安全なイメージを作成します。

【.dockerignoreの活用】
不要ファイル(Gitリポジトリ、テストコード、ローカル設定ファイルなど)がイメージに含まれないように設定。

.git
node_modules
*.env

おわりに

ここまで、Dockerを使ってアプリケーションをパッケージングする方法を解説してきました。Dockerは開発環境の差異による「動かない問題」を解消し、開発から本番まで一貫性を保てる強力なツールです。

最初はDockerfileの作成やコマンド操作に戸惑うこともありますが、慣れてくると「一度環境を整えればどこでも動く」という安心感が得られるはずです。特にチーム開発やCI/CDの導入を見据えると、そのメリットはさらに大きくなります。

日本仮想化技術株式会社
Sierやベンチャー企業を経て、現在は日本仮想化技術でDevOps支援サービス「かんたんDevOps」のDev側を担当。「DevOpsを通じて開発者体験を最大化する」をミッションに理想的な開発環境の実現を目指して技術調査や仕組み作りを行っている。

連載バックナンバー

システム開発技術解説
第10回

「Docker」を使ってアプリケーションをパッケージングしよう

2025/7/15
第10回の今回は、「Docker」を使ってアプリケーションをパッケージングする方法について、基本からステップバイステップで解説します。
システム開発技術解説
第9回

DevOpsを支えるレビュー運用の基本とGitHub活用法

2025/6/24
第9回の今回は、GitHubを用いたコードレビューの基本と実践方法について解説します。
システム開発技術解説
第8回

「Git」によるスムーズなチーム開発を支えるルール作りと活用法を学ぼう

2025/6/3
第8回の今回は、チーム開発における「Git」運用のルール整備と、効率的なコードレビューを実現するための活用法を解説します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています