Dockerの普及に伴い、コンテナのセキュリティは開発者にとって避けて通れないテーマとなりました。「コンテナだから安全」という認識は誤りであり、適切な対策を怠れば深刻な脆弱性を抱えることになります。
この記事では、Dockerコンテナを安全に運用するための10のルールを、具体的なコード例と設定方法とともに解説します。開発環境から本番環境まで、すぐに適用できる実践的なセキュリティ対策です。
ルール1:非rootユーザーでコンテナを実行する
Dockerコンテナはデフォルトでroot権限で実行されます。これはコンテナが侵害された場合、攻撃者にroot権限を与えることを意味します。必ず専用のユーザーを作成し、そのユーザーでアプリケーションを実行しましょう。
Dockerfileでのユーザー設定
# ユーザーの作成と切り替え
FROM node:20-alpine
# アプリケーション用ユーザーを作成
RUN addgroup --system appgroup && \
adduser --system --ingroup appgroup appuser
WORKDIR /app
COPY --chown=appuser:appgroup . .
RUN npm ci --omit=dev
# ユーザーを切り替え
USER appuser
EXPOSE 3000
CMD ["node", "index.js"]
USER命令以降のすべてのコマンドは、指定したユーザーの権限で実行されます。--chownフラグでファイルの所有権も適切に設定することが重要です。
実行時にユーザーを指定する方法
# docker runで実行時にユーザーを指定
docker run --user 1000:1000 myapp
# docker-composeで指定
services:
app:
image: myapp
user: "1000:1000"
DockerfileでUSERを指定していない場合でも、実行時に--userフラグで上書きできます。ただし、Dockerfileに組み込む方が確実です。
ルール2:信頼できるベースイメージを使用する
ベースイメージの選択は、コンテナセキュリティの基盤です。不明な出所のイメージや、メンテナンスされていないイメージを使うことは、未知の脆弱性を取り込むリスクがあります。
推奨されるベースイメージ
- 公式イメージ:Docker Hubの「Official Image」バッジ付きイメージ
- Verified Publisher:Docker Hubで検証済みの発行元のイメージ
- distrolessイメージ:Google提供の最小構成イメージ
- Chainguardイメージ:セキュリティに特化した最小イメージ
# 推奨: 公式イメージを使用
FROM node:20-alpine
# 推奨: distrolessイメージを使用
FROM gcr.io/distroless/nodejs20-debian12
# 非推奨: 不明なイメージ
FROM random-user/mysterious-node-image
イメージのダイジェストを固定する
タグだけでなく、ダイジェスト(SHA256ハッシュ)でイメージを固定すると、サプライチェーン攻撃を防げます。
# タグだけでは同じタグで別の内容に差し替えられる可能性がある
FROM node:20-alpine
# ダイジェストで固定すれば、イメージの内容が保証される
FROM node:20-alpine@sha256:abc123def456...
ダイジェストはdocker inspectコマンドで確認できます。
docker inspect --format='{{index .RepoDigests 0}}' node:20-alpine
ルール3:イメージの脆弱性スキャンを実施する
コンテナイメージには、ベースイメージやインストールしたパッケージに含まれる既知の脆弱性が潜んでいる可能性があります。定期的にスキャンを行い、脆弱性を検出・修正することが重要です。
Docker Scoutによるスキャン
# Docker Scout(Docker Desktop統合済み)
docker scout cves myapp:latest
# クリティカルと高レベルの脆弱性のみ表示
docker scout cves --only-severity critical,high myapp:latest
# SBOMの生成
docker scout sbom myapp:latest
Trivyによるスキャン
# Trivyのインストール(macOS)
brew install aquasecurity/trivy/trivy
# イメージスキャン
trivy image myapp:latest
# 高以上の脆弱性のみ表示
trivy image --severity HIGH,CRITICAL myapp:latest
# CI/CDで使えるJSON出力
trivy image --format json --output report.json myapp:latest
CI/CDパイプラインへの統合
# GitHub Actionsでの脆弱性スキャン
name: Security Scan
on:
push:
branches: [main]
pull_request:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
ルール4:最小限のイメージを構築する
イメージに含まれるパッケージやファイルが少ないほど、攻撃対象領域が狭くなります。マルチステージビルドと最小ベースイメージを組み合わせて、本番イメージを極限まで削ぎ落としましょう。
不要なパッケージを除外する
# 悪い例: 不要なツールがインストールされる
RUN apt-get update && apt-get install -y \
curl \
wget \
vim \
net-tools \
python3
# 良い例: 必要最小限のパッケージのみ
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
--no-install-recommendsフラグは、推奨パッケージの自動インストールを防ぎます。また、rm -rf /var/lib/apt/lists/*でパッケージリストのキャッシュを削除し、イメージサイズを削減します。
シェルを含まないイメージを使う
# distrolessやscratchイメージにはシェルがない
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
シェルが含まれないイメージでは、攻撃者がコンテナに侵入してもshやbashを実行できないため、被害の拡大を防げます。
ルール5:機密情報をイメージに含めない
APIキー、データベースパスワード、秘密鍵などの機密情報をDockerイメージに埋め込んではいけません。イメージはレジストリに保存され、チーム全体でアクセス可能なため、機密情報が漏洩するリスクがあります。
やってはいけないパターン
# 危険: 環境変数にハードコード
ENV DATABASE_URL=postgres://user:password@db:5432/mydb
ENV API_KEY=sk-1234567890abcdef
# 危険: ファイルをコピー
COPY .env /app/.env
COPY credentials.json /app/credentials.json
安全な機密情報の渡し方
# 方法1: 実行時に環境変数として渡す
docker run -e DATABASE_URL="postgres://user:pass@db/mydb" myapp
# 方法2: docker-composeでenv_fileを使う
services:
app:
image: myapp
env_file:
- .env # .envファイルはイメージに含めない
# 方法3: Docker Secretsを使う(Docker Swarm)
echo "my-secret-password" | docker secret create db_password -
services:
app:
image: myapp
secrets:
- db_password
secrets:
db_password:
external: true
ビルド時に必要な機密情報
ビルド時にプライベートリポジトリへのアクセスが必要な場合は、BuildKitのシークレットマウントを使います。
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
# シークレットをマウント(イメージレイヤーに残らない)
RUN --mount=type=secret,id=npmrc,target=/app/.npmrc \
npm ci
COPY . .
RUN npm run build
# ビルド時にシークレットを渡す
docker build --secret id=npmrc,src=$HOME/.npmrc -t myapp .
ルール6:リソースを制限する
コンテナのCPU、メモリ、PIDなどのリソースを制限することで、1つのコンテナが暴走してホスト全体に影響を与えることを防ぎます。
docker runでのリソース制限
# メモリ制限
docker run --memory=512m --memory-swap=512m myapp
# CPU制限
docker run --cpus=1.5 myapp
# PID数制限(フォーク爆弾対策)
docker run --pids-limit=100 myapp
# 読み取り専用ファイルシステム
docker run --read-only --tmpfs /tmp myapp
# 組み合わせ
docker run \
--memory=512m \
--memory-swap=512m \
--cpus=1.5 \
--pids-limit=100 \
--read-only \
--tmpfs /tmp \
myapp
docker-composeでのリソース制限
services:
app:
image: myapp
deploy:
resources:
limits:
cpus: '1.5'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
read_only: true
tmpfs:
- /tmp
pids_limit: 100
ルール7:ネットワークを適切に分離する
Dockerのデフォルトブリッジネットワークでは、すべてのコンテナが互いに通信可能です。マイクロサービスアーキテクチャでは、サービスごとにネットワークを分離し、必要な通信だけを許可しましょう。
カスタムネットワークの設計
# docker-compose.yml
services:
frontend:
image: nginx
networks:
- frontend-net
ports:
- "80:80"
api:
image: myapi
networks:
- frontend-net # フロントエンドからのアクセスを許可
- backend-net # データベースへのアクセスを許可
db:
image: postgres:16
networks:
- backend-net # APIからのアクセスのみ許可
# ポートを公開しない(外部からアクセス不可)
networks:
frontend-net:
driver: bridge
backend-net:
driver: bridge
internal: true # 外部への通信を遮断
この構成では、frontendからdbに直接アクセスすることはできません。apiを経由する必要があり、データベースへの不正アクセスリスクを低減します。
ルール8:Linuxケーパビリティを最小化する
Dockerコンテナにはデフォルトで複数のLinuxケーパビリティが付与されています。不要なケーパビリティを削除し、必要なものだけを追加することで、攻撃対象を限定できます。
ケーパビリティの制御
# すべてのケーパビリティを削除し、必要なものだけ追加
docker run \
--cap-drop=ALL \
--cap-add=NET_BIND_SERVICE \
myapp
# docker-compose.yml
services:
app:
image: myapp
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # 1024未満のポートにバインド
security_opt:
- no-new-privileges:true # 特権昇格を防止
no-new-privilegesオプションは、コンテナ内のプロセスがsetuidなどで特権を昇格することを防ぎます。
ルール9:ログとモニタリングを実装する
セキュリティインシデントの早期発見と事後分析のために、適切なログ収集とモニタリングの仕組みを構築しましょう。
ログドライバーの設定
# JSON形式でログサイズを制限
docker run \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
myapp
# docker-compose.yml
services:
app:
image: myapp
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
tag: "{{.Name}}/{{.ID}}"
ヘルスチェックの設定
# Dockerfile内でヘルスチェックを定義
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
# docker-compose.yml
services:
app:
image: myapp
healthcheck:
test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 5s
ルール10:定期的にイメージを更新する
コンテナセキュリティは継続的な取り組みです。ベースイメージや依存関係を定期的に更新し、既知の脆弱性を修正しましょう。
自動更新の仕組み
# GitHub Actionsで週次の自動更新チェック
name: Weekly Security Update
on:
schedule:
- cron: '0 9 * * 1' # 毎週月曜9時
workflow_dispatch:
jobs:
update-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build with latest base image
run: docker build --pull --no-cache -t myapp:check .
- name: Scan for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:check
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Create issue if vulnerabilities found
if: failure()
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Security: Base image vulnerabilities detected',
body: 'Weekly scan detected vulnerabilities. Please update base images.',
labels: ['security', 'automated']
})
Dependabotによるベースイメージ更新
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: docker
directory: "/"
schedule:
interval: weekly
reviewers:
- "security-team"
まとめ:セキュリティチェックリスト
Dockerコンテナのセキュリティは、一度設定すれば終わりではなく、継続的に見直し改善していくプロセスです。最後に、本記事で紹介した10のルールをチェックリストとしてまとめます。
- ルール1:非rootユーザーでコンテナを実行しているか
- ルール2:信頼できるベースイメージを使用しているか
- ルール3:イメージの脆弱性スキャンをCI/CDに組み込んでいるか
- ルール4:最小限のパッケージのみ含むイメージを構築しているか
- ルール5:機密情報をイメージに含めていないか
- ルール6:CPU・メモリ・PIDのリソース制限を設定しているか
- ルール7:コンテナ間のネットワークを適切に分離しているか
- ルール8:Linuxケーパビリティを最小化しているか
- ルール9:ログとヘルスチェックを設定しているか
- ルール10:イメージの定期的な更新体制があるか
すべてを一度に導入する必要はありません。まずはルール1(非root実行)とルール3(脆弱性スキャン)から始め、段階的に対策を広げていくことをおすすめします。セキュリティは「やりすぎ」ということはなく、一つひとつの対策が積み重なって堅牢なコンテナ運用環境を実現します。
関連記事
AIエージェント開発入門|自律型AIの仕組みと構築方法を解説【2026年版】
AI駆動コーディングワークフロー|Claude Code・Cursor・Copilotの実践的使い分け
プロンプトエンジニアリング上級編|Chain-of-Thought・Few-Shot・ReActの実践
APIレート制限の設計と実装|トークンバケット・スライディングウィンドウ解説
APIバージョニング戦略|URL・ヘッダー・クエリパラメータの使い分け
BIツール入門|Metabase・Redash・Looker Studioでデータ可視化する方法
チャットボット開発入門|LINE Bot・Slack Botの構築方法と活用事例
CI/CDパイプラインの基礎|継続的インテグレーション・デリバリーの全体像