今日のペースの速いソフトウェア開発の世界では、製品チームは、機能の構築、アップデートの出荷、ユーザーのニーズへのリアルタイム対応など、迅速に行動することが期待されています。しかし、迅速に行動するということは、品質やセキュリティに妥協することを意味するものではありません。
最新のツールのおかげで、開発者はデリバリーを加速しながら高い基準を維持できるようになりました。以前の記事では、Testcontainers が内部開発ループ内で高速で信頼性の高い統合テストを可能にすることで、シ フトレフト テストをどのようにサポート するかを検討しました。この投稿では、このシフトレフトアプローチのセキュリティ面と、Dockerが開発ライフサイクルの早い段階でセキュリティを移行するのにどのように役立つかを、実際の例を使用して見ていきます。
シフトレフトアプローチ: 映画カタログ API のテスト
簡単な デモプロジェクト を使用して、ワークフローについて説明します。これは、PostgreSQLによってサポートされ、TestcontainersでテストされたNode.js + TypeScript APIです。
ムービー API エンドポイント:
方式 |
エンドポイント |
形容 |
|
---|---|---|---|
投稿 |
/映画 |
カタログに新しいムービーを追加する |
|
取得 |
/映画 |
タイトル順に並べ替えてすべての映画を取得する |
|
取得 |
/ムービー/検索?q=... |
タイトルまたは説明で映画を検索する(あいまい一致) |
このアプリを本番環境にデプロイする前に、正しく機能し、重大な脆弱性がないことを確認する必要があります。
Testcontainers を使用したシフトレフト テスト: まとめ
Testcontainers を使用してデータベースとアプリケーションの両方のコンテナをスピンアップすることで、実際の PostgreSQL インスタンスに対してアプリケーションを検証します。Testcontainers の主な利点は、テスト実行中にこれらのコンテナを動的に作成することです。Testcontainers ライブラリのもう 1 つの機能は、Dockerfile から直接コンテナーを起動できることです。これにより、コンテナ化されたアプリケーションをデータベースなどの必要なサービスとともに実行し、APIまたはエンドツーエンド(E2E)レベルでアプリケーションをテストするために必要なローカル環境を効果的に再現できます。このアプローチは、品質保証の追加レイヤーを提供し、内部開発ループにさらに多くのテストをもたらします。
Testcontainers が開発者の内部ループへのシフトレフトテストアプローチを可能にする方法の詳細については、入 門ブログ投稿を参照してください。
PostgreSQLや開発中のアプリケーションを含むテスト環境を準備するbeforeAllセットアップは、Dockerfileから開始されます。
beforeAll(async () => {
const network = await new Network().start();
// 1. Start Postgres
db = await new PostgreSqlContainer("postgres:17.4")
.withNetwork(network)
.withNetworkAliases("postgres")
.withDatabase("catalog")
.withUsername("postgres")
.withPassword("postgres")
.withCopyFilesToContainer([
{
source: path.join(__dirname, "../dev/db/1-create-schema.sql"),
target: "/docker-entrypoint-initdb.d/1-create-schema.sql"
},
])
.start();
// 2. Build movie catalog API container from the Dockerfile
const container = await GenericContainer
.fromDockerfile("../movie-catalog")
.withTarget("final")
.withBuildkit()
.build();
// 3. Start movie catalog API container with environment variables for DB connection
app = await container
.withNetwork(network)
.withExposedPorts(3000)
.withEnvironment({
PGHOST: "postgres",
PGPORT: "5432",
PGDATABASE: "catalog",
PGUSER: "postgres",
PGPASSWORD: "postgres",
})
.withWaitStrategy(Wait.forListeningPorts())
.start();
}, 120000);
これで、映画カタログ API をテストできます。
it("should create and retrieve a movie", async () => {
const baseUrl = `http://${app.getHost()}:${app.getMappedPort(3000)}`;
const payload = {
title: "Interstellar",
director: "Christopher Nolan",
genres: ["sci-fi"],
releaseYear: 2014,
description: "Space and time exploration"
};
const response = await axios.post(`${baseUrl}/movies`, payload);
expect(response.status).toBe(201);
expect(response.data.title).toBe("Interstellar");
}, 120000);
このアプローチにより、次のことを検証できます。
- アプリケーションは適切にコンテナ化され、正常に起動します。
- API は、実際のデータベースを持つコンテナー化された環境で正しく動作します。
しかし、それは品質ストーリーの一部にすぎません。次に、開発中のアプリケーションのセキュリティ面に注目しましょう。
Docker Scout と Docker Hardened イメージの紹介
最新のベスト プラクティスに従うために、 アプリをコンテナー化 し、最終的に運用環境にデプロイする必要があります。その前に、 Docker Scout を使用してイメージが安全であることを確認する必要があります。
Dockerfileは多段階のビルドアプローチを採用しており、ノード22-slimイメージに基づいています。
###########################################################
# Stage: base
# This stage serves as the base for all of the other stages.
# By using this stage, it provides a consistent base for both
# the dev and prod versions of the image.
###########################################################
FROM node:22-slim AS base
WORKDIR /usr/local/app
RUN useradd -m appuser && chown -R appuser /usr/local/app
USER appuser
COPY --chown=appuser:appuser package.json package-lock.json ./
###########################################################
# Stage: dev
# This stage is used to run the application in a development
# environment. It installs all app dependencies and will
# start the app in a dev mode that will watch for file changes
# and automatically restart the app.
###########################################################
FROM base AS dev
ENV NODE_ENV=development
RUN npm ci --ignore-scripts
COPY --chown=appuser:appuser ./src ./src
EXPOSE 3000
CMD ["npx", "nodemon", "src/app.js"]
###########################################################
# Stage: final
# This stage serves as the final image for production. It
# installs only the production dependencies.
###########################################################
# Deps: install only prod deps
FROM base AS prod-deps
ENV NODE_ENV=production
RUN npm ci --production --ignore-scripts && npm cache clean --force
# Final: clean prod image
FROM base AS final
WORKDIR /usr/local/app
COPY --from=prod-deps /usr/local/app/node_modules ./node_modules
COPY ./src ./src
EXPOSE 3000
CMD [ "node", "src/app.js" ]
SBOM と来歴メタデータを使用してイメージを構築しましょう。まず、コンテナー化されたイメージストアが Docker Desktopで有効になっていることを確認します。また、buildxコマンド(dockerビルドを拡張するDocker CLIプラグイン)を–provenance=trueおよび–sbom=trueフラグとともに使用します。これらのオプションは 、ビルド構成証明を イメージに添付し、Docker Scoutは、より詳細で正確なセキュリティ分析を提供するために使用します。
docker buildx build --provenance=true --sbom=true -t movie-catalog-service:v1 .
次に、セキュリティポリシーを使用してDocker組織を設定し、Docker Scoutでイメージをスキャンします。
docker scout config organization demonstrationorg
docker scout quickview movie-catalog-service:v1

図 1: ノードの Docker Scout CLI クイックビュー出力:22 ベースの movie-catalog-service イメージ
Docker Scoutは、Docker Desktopを介した視覚的な分析も提供します。

図 2: ノードのDocker DesktopのイメージレイヤーとCVEビュー:22 ベースのmovie-catalog-serviceイメージ
この例では、アプリケーション層に脆弱性は見つかりませんでした。ただし、ベースノードによっていくつかのCVEが導入されました:重大度の高いCVE-2025-6020を含む22-slimイメージ、Debian12に存在する脆弱性。これは、Debian 12 に基づくNode.jsイメージがこの脆弱性を継承することを意味します。これに対処する一般的な方法は、このCVEを含まないAlpineベースのNodeイメージに切り替えることです。ただし、Alpine は glibc の代わりに musl libc を使用するため、アプリケーションのランタイム要件とデプロイ環境によっては互換性の問題が発生する可能性があります。
では、より安全で互換性のある代替手段は何でしょうか?
そこで、 Docker Hardened Images (DHI) の出番です。これらのイメージはディストリビューションレスの哲学に従っており、不要なコンポーネントを削除して攻撃対象領域を大幅に減らします。その結果は?より高速にプルし、より無駄なく実行し、運用ワークロードに既定で安全な基盤を提供する小さなイメージ:
- 悪用可能なCVEはほぼゼロ:継続的に更新され、脆弱性がスキャンされ、署名された証明とともに公開され、パッチ疲労を最小限に抑え、誤検知を排除します。
- シームレスな移行: 一般的な基本イメージのドロップイン置換と、マルチステージ ビルドで -dev バリアントを使用できます。
- 攻撃対象領域を最大 95% 縮小: シェルとパッケージ マネージャーを備えた完全な OS スタックを含む従来の基本イメージとは異なり、ディストリビューションレス イメージは、アプリの実行に必要な必要なもののみを保持します。
- 組み込みのサプライ チェーン セキュリティ: 各イメージには、署名された SBOM、VEX ドキュメント、および監査対応パイプラインの SLSA 出所が含まれています。
開発者にとって、DHIはCVE関連の中断が少なく、CI/CDパイプラインが高速化され、信頼できるイメージを安心して使用できることを意味します。
Docker 強化イメージへの切り替え
Docker Hardened Image への切り替えは簡単です。必要なのは、ベースイメージノード22-slimを同等のDHIに置き換えることだけです。
Docker Hardened Images には、次の 2 つのバリエーションがあります。
- Dev バリアント (demonstrationorg/dhi-node:22-dev) – シェルとパッケージマネージャーが含まれているため、ビルドとテストに適しています。
- ランタイムバリアント (demonstrationorg/dhi-node:22) – 必要なもののみに削減され、本番環境に最小限で安全なフットプリントを提供します。
これにより、多段階のDockerfileでの使用に最適です。開発イメージでアプリをビルドし、ビルドされたアプリケーションをランタイムイメージにコピーして、本番環境のベースとして機能します。
更新されたDockerfileは次のようになります。
###########################################################
# Stage: base
# This stage serves as the base for all of the other stages.
# By using this stage, it provides a consistent base for both
# the dev and prod versions of the image.
###########################################################
# Changed node:22 to dhi-node:22-dev
FROM demonstrationorg/dhi-node:22-dev AS base
WORKDIR /usr/local/app
# DHI comes with nonroot user built-in.
COPY --chown=nonroot package.json package-lock.json ./
###########################################################
# Stage: dev
# This stage is used to run the application in a development
# environment. It installs all app dependencies and will
# start the app in a dev mode that will watch for file changes
# and automatically restart the app.
###########################################################
FROM base AS dev
ENV NODE_ENV=development
RUN npm ci --ignore-scripts
# DHI comes with nonroot user built-in.
COPY --chown=nonroot ./src ./src
EXPOSE 3000
CMD ["npx", "nodemon", "src/app.js"]
###########################################################
# Stage: final
# This stage serves as the final image for production. It
# installs only the production dependencies.
###########################################################
# Deps: install only prod deps
FROM base AS prod-deps
ENV NODE_ENV=production
RUN npm ci --production --ignore-scripts && npm cache clean --force
# Final: clean prod image
# Changed base to dhi-node:22
FROM demonstrationorg/dhi-node:22 AS final
WORKDIR /usr/local/app
COPY --from=prod-deps /usr/local/app/node_modules ./node_modules
COPY ./src ./src
EXPOSE 3000
CMD [ "node", "src/app.js" ]
新しいイメージを再構築してスキャンしましょう。
docker buildx build --provenance=true --sbom=true -t movie-catalog-service-dhi:v1 .
docker scout quickview movie-catalog-service-dhi:v1

図 3: dhi-node の Docker Scout CLI クイックビュー出力:22 ベースの movie-catalog-service イメージ
ご覧のとおり、Docker Hardened Image のクリーンで最小限のフットプリントのおかげで、重大で高い CVE はすべてなくなりました。
DHI を使用する主な利点の 1 つは、DHI が提供するセキュリティ SLA です。新しいCVEが発見された場合、DHIチームは次の解決を約束します。
- パッチが利用可能になってから 7 日以内の重大かつ高い脆弱性、
- 30日以内に中程度および低の脆弱性。
これは、CVE修復の負担を大幅に軽減し、開発者が脆弱性を追いかけるのではなく、イノベーションと機能開発に集中する時間を増やすことができることを意味します。
Docker Scoutとのイメージの比較
また、ディストリビューションレスの強化イメージを使用するイメージ サイズとパッケージ数の利点についても見てみましょう。
Docker Scoutは、2つのイメージを分析および比較できる便利なコマンドdocker scout compareを提供します。これを使用して、node:22-slim と dhi-node:22 ベースのイメージのサイズとパッケージのフットプリントの違いを評価します。
docker scout compare local://movie-catalog-service:v1 --to local://movie-catalog-service-dhi:v1

図 4: node:22 とdhi-node:22 ベースのmovie-catalog-serviceイメージの比較
ご覧のとおり、元のnode:22-slimベースのイメージはサイズが80MBで、427パッケージが含まれていましたが、dhi-node:22ベースのイメージは123パッケージのみでわずか41MBです。
Docker Hardened Image に切り替えることで、イメージ サイズを 50 % 近く削減し、パッケージの数を 3 倍以上削減し、攻撃対象領域を大幅に減らしました。
最終ステップ: ローカル API テストで検証する
最後になりましたが、DHI ベース イメージに移行した後も、アプリケーションが期待どおりに機能することを確認する必要があります。
Testcontainers ベースのテストをすでに実装しているため、API がアクセス可能であり、正しく動作することを簡単に確認できます。
npm test コマンドを使用してテストを実行しましょう。

図 5: ローカルAPIテスト実行結果
ご覧のとおり、コンテナーは正常にビルドされ、起動されました。20秒もかからずに、アプリケーションが正しく機能し、Postgresと適切に統合されていることを確認できました。
この時点で、アプリケーションが安全で完全に機能していると確信して、変更をリモート リポジトリにプッシュし、次のタスクに進むことができます。
外部セキュリティツールとのさらなる統合
最小限で安全な基本イメージを提供することに加えて、Docker Hardened Images には包括的な構成証明セットが含まれています。これらには、ビルド プロセス中に使用されるすべてのコンポーネント、ライブラリ、依存関係の詳細を示すソフトウェア部品表 (SBOM) や、脆弱性悪用可能性交換 (VEX) が含まれます。VEX は脆弱性に関するコンテキストに基づいた洞察を提供し、特定の環境で脆弱性が実際に悪用可能かどうかを指定し、チームが修復の優先順位を付けるのに役立ちます。
コードの変更をコミットし、アプリケーションを構築し、コンテナー イメージをプッシュしたとします。次に、Grype や Trivy など、すでに使用している外部スキャン ツールを使用してセキュリティ体制を確認します。これには、Docker Scoutが生成できる互換性のある形式の脆弱性情報が必要です。
まず、docker scout attestコマンドを使用して、使用可能な構成証明のリストを表示できます。
docker scout attest list demonstrationorg/movie-catalog-service-dhi:v1 --platform linux/arm64
このコマンドは、イメージにバンドルされている構成証明の詳細なリストを返します。たとえば、2 つの OpenVEX ファイルが表示される場合があります: 1 つは DHI ベース イメージ用、もう 1 つはイメージに固有のカスタム例外 (no-dsa など) 用です。
次に、この情報を外部ツールと統合するために、VEX データを vex.json ファイルにエクスポートします。Docker Scout v1を起動します。18。3docker scout vex get コマンドを使用して、すべての VEX 認証からマージされた VEX ドキュメントを取得できます。
docker scout vex get demonstrationorg/movie-catalog-service-dhi:v1 --output vex.json
これにより、指定された画像のすべての VEX ステートメントを含むvex.json ファイルが生成されます。VEX をサポートするツールは、このファイルを使用して、既知の悪用不可能な CVE を抑制できます。
Grype または Trivy で VEX 情報を使用するには、スキャン中に –vex フラグを渡します。
trivy image demonstrationorg/movie-catalog-service-dhi:v1 --vex vex.json
これにより、Docker Scoutが提供する脆弱性コンテキストの同じセットを活用して、セキュリティスキャンの結果がツール間で一貫していることが保証されます。
結論
シフトレフトは、単なる初期テストではありません。これは、最初から安全で本番環境に対応したソフトウェアを構築するための積極的な考え方です。
このシフトレフトアプローチは、以下を組み合わせています。
- Testcontainersを使用した実際のインフラストラクチャテスト
- Docker Scoutによるエンドツーエンドのサプライチェーンの可視性と実用的な洞察
- Docker Hardened Images による信頼できる最小限の基本イメージ
これらのツールを組み合わせることで、問題を早期に発見し、コンプライアンスを向上させ、ソフトウェアサプライチェーンにおけるセキュリティリスクを軽減できます。