Fargate/LambdaのDockerビルドでCodeArtifactのトークンどう渡す?(ARG/ENVはNG)

記事タイトルとURLをコピーする

はじめに

こんにちは!三宅です! 最近運動不足でエアロバイクを買いました。

今回は、AWS Fargate や AWS Lambda で利用する Docker コンテナのビルド時に、AWS CodeArtifact のプライベート Python パッケージを安全にインストールする方法を紹介します。

ポイントは Docker Build Secrets--mount=type=secret)を使うことです。

これを使うと、認証トークンを Docker イメージに残さずに扱えます。

この記事は以下のような方を対象としています。

  • Dockerコンテナ環境でAWS CodeArtifactのプライベートパッケージを使いたい方
  • Dockerfileに認証情報を渡す方法について不安を感じている方
  • Docker Build Secretsの存在は知っているけど、具体的な使い方を知りたい方

前提条件

  • AWS CodeArtifactにプライベートPythonリポジトリが構築済み
  • ローカル開発でPythonパッケージマネージャに uv を使用(pip環境でも同様の手順で対応できます)

今回やること

AWS CodeArtifact にホストしたプライベートPythonパッケージを、Dockerコンテナ内で使いたいというのが要件です。 Dockerイメージをビルドする以下の場面を想定しています。

  • AWS Fargate — Amazon Elastic Container Service (ECS) タスクとして動かすコンテナ
  • AWS Lambda — コンテナイメージでデプロイするLambda関数

いずれのケースでも、Dockerfile内の pip install 時にCodeArtifactの認証トークンを渡す必要があり、トークンをイメージに残さず安全に扱わないといけません。

全体の流れ

今回の構成では、以下の流れでビルドしています。

  1. トークン取得: 自作スクリプト codeartifact.sh でCodeArtifactの認証トークンを取得し、環境変数にセット
  2. requirements.txt 生成: トークンを使ってCodeArtifactの依存を解決し、requirements.txt を生成
  3. Dockerビルド: docker build --secret でトークンをシークレットとして渡す
  4. Dockerfile内: RUN --mount=type=secret で一時マウントして pip install

ポイントは、ステップ3のDocker Build Secretsです。まずは、なぜこの方法が必要なのかを説明します。

Dockerfileへの認証トークンの渡し方を検討する

なぜ認証トークンを渡す必要があるのか

pip はデフォルトで PyPI(公開リポジトリ)を参照します。CodeArtifact のプライベートパッケージをインストールするには、pip の参照先を CodeArtifact に向ける設定が必要です。

通常は aws codeartifact login --tool pip コマンドを使います。このコマンドは AWS 認証情報を使って CodeArtifact から認可トークンを取得し、pipの参照先を CodeArtifact に向けてくれます。

しかし、Dockerfile 内でこのコマンドを使うには AWS 認証情報の受け渡しが必要になるため、ログインコマンドを使用しない方が良いと判断しました。

そこで、ログインコマンドを使わずに pip を設定する方法を取ります。index-url は pip がパッケージを探しに行く先のURLで、デフォルトでは PyPI(https://pypi.org/simple/)を指しています。ここに認証トークンを埋め込んだ CodeArtifact のURL(https://aws:<トークン>@<ドメイン>.d.codeartifact...)を設定することで、login コマンドなしで CodeArtifact を参照できます。

詳細は公式ドキュメントの ログインコマンドを使用せずに pip を設定する を参照してください!

ただし、このURL自体が認証トークンを含む機密情報です。次のセクションで、これをどう安全に渡すかを検討します。

ARG / ENV ではイメージにトークンが残る

まず思いつくのは ARGENV でトークン入りのURLを渡す方法です。

# やってはいけない例
ARG CODEARTIFACT_URL
RUN pip install --index-url ${CODEARTIFACT_URL} -r requirements.txt

これでもインストール自体は成功します。しかし、ARGENVで渡した値は最終イメージにそれらが残ってしまうため安全ではないです。

プライベートリポジトリの認証トークンがイメージに焼き込まれるのは、セキュリティ上好ましくありません。公式ドキュメントでも、ARGENV で機密情報を渡すことは非推奨とされています。

SecretsUsedInArgOrEnv

Docker Build Secrets について

Docker Build Secrets は、Dockerのビルド時にパスワードやAPIトークンなどの機密情報を安全に扱うための仕組みです。

ARGENV とは違い、ビルド時にだけシークレットを参照でき、最終的なイメージには残りません。

ビルドシークレットの使用

仕組みはシンプルです。

  1. docker build--secret を指定してシークレットを渡す
  2. Dockerfile の RUN--mount=type=secret を使って一時的にマウントする
  3. 実行中だけ /run/secrets/ に存在し、イメージには残らない

次から、実際にどう実装したかを見ていきます!

Docker Build Secretsを使ったDockerコンテナのビルドを実装してみる

CodeArtifact認証トークン取得のラッパースクリプトを作成する

Docker Build Secrets では、環境変数経由でシークレットを渡せます。

まずは、CodeArtifact の認証トークンを取得して環境変数にセットするスクリプトを用意します。

#!/bin/bash
# codeartifact.sh

# 設定
DOMAIN=""
REPO=""
REGION=""

AWS_PROFILE="your-profile"

# AWSアカウントID取得(ハードコード不要)
ACCOUNT_ID=$(aws sts get-caller-identity \
    --query Account \
    --output text \
    --profile "$AWS_PROFILE")

if [ -z "$ACCOUNT_ID" ] || [ "$ACCOUNT_ID" = "None" ]; then
    echo "Error: Failed to get AWS Account ID. Check your profile or AWS credentials." >&2
    exit 1
fi

# トークン取得
CA_TOKEN=$(aws codeartifact get-authorization-token \
    --domain "$DOMAIN" \
    --domain-owner "$ACCOUNT_ID" \
    --region "$REGION" \
    --query authorizationToken \
    --output text \
    --profile "$AWS_PROFILE")

if [ -z "$CA_TOKEN" ] || [ "$CA_TOKEN" = "None" ]; then
    echo "Error: Failed to get CodeArtifact token" >&2
    exit 1
fi

# pip を使っている場合は、UV_DEFAULT_INDEX → PIP_INDEX_URL、UV_INDEX → PIP_EXTRA_INDEX_URL に置き換える
UV_DEFAULT_INDEX="https://aws:${CA_TOKEN}@${DOMAIN}-${ACCOUNT_ID}.d.codeartifact.ap-northeast-1.amazonaws.com/pypi/${REPO}/simple/" \
UV_INDEX="https://pypi.org/simple/" \
exec "$@"

ポイントをいくつか補足します。

  • 環境変数名について
    • UV_DEFAULT_INDEX は uv がパッケージを探しに行くデフォルトのインデックスURLです
    • UV_INDEX は追加のインデックスURLで、CodeArtifact にないパッケージは PyPI から取得するために指定しています
    • ローカル開発では ./codeartifact.sh uv sync のように実行すれば、uv がこれらの環境変数を直接参照してくれます。Docker ビルド時は --secret id=ca_url,env=UV_DEFAULT_INDEX でこの値をシークレットとして渡すことができます
    • uv 環境変数リファレンス
  • UV_DEFAULT_INDEX=... exec "$@" の書き方について
    • 変数=値 コマンド と書くと、そのコマンドの実行中だけ環境変数が有効になります。export だとトークンがシェルに残ってしまうのが嫌だったので、この書き方を採用しました
    • exec "$@" はスクリプト自身を引数のコマンドで置き換える書き方で、./codeartifact.sh uv sync のように実行できます

requirements.txt を生成する

Dockerビルドの前に、requirements.txt を用意します。pyproject.toml で依存関係を管理している場合は、uv pip compile で生成できます。

プライベートパッケージが依存に含まれている場合、依存解決の時点でCodeArtifactへのアクセスが必要です。ここでも codeartifact.sh が使えます。

./codeartifact.sh uv pip compile pyproject.toml > requirements_layer/requirements.txt

ビルドコマンドでシークレットを渡す

requirements.txt が用意できたら、codeartifact.sh 経由で docker build を実行します。

./codeartifact.sh docker build \
    --secret id=ca_url,env=UV_DEFAULT_INDEX \
    -t my-app:latest \
    -f Dockerfile .

codeartifact.sh 経由で実行することで UV_DEFAULT_INDEX にトークン入りURLがセットされた状態になります。--secret id=ca_url,env=UV_DEFAULT_INDEX は、その環境変数の値を ca_url というIDのシークレットとしてビルドに渡すという意味です。

Dockerfileでシークレットを受け取る

Dockerfile側では RUN --mount=type=secret でシークレットを参照します。

以下はAWS Lambda向けのDockerfileの例です。

FROM public.ecr.aws/lambda/python:3.13-arm64

WORKDIR /app

COPY requirements_layer/requirements.txt .

# Build Secretsでトークンを環境変数として直接マウントしてpip install
RUN --mount=type=secret,id=ca_url,env=PIP_INDEX_URL \
    PIP_EXTRA_INDEX_URL="https://pypi.org/simple/" \
    pip install --no-cache-dir -r requirements.txt

COPY src/ ${LAMBDA_TASK_ROOT}

CMD ["app.handler.main"]

ポイントをいくつか補足します。

  • --mount=type=secret,id=ca_url,env=PIP_INDEX_URL で、シークレットが PIP_INDEX_URL 環境変数として直接マウントされます。
  • pip installPIP_INDEX_URL を自動的に参照するので、--index-url オプションも不要です
  • この RUN ステップが終わると環境変数は消え、イメージのどのレイヤーにもトークンは残りません
  • ベースイメージを変えれば、Fargate向けでもそのまま同じパターンが使えます

Build secrets | target

まとめ

  • ARGENV でトークンを渡すとイメージのレイヤーに残ってしまうので、Docker Build Secrets--mount=type=secret)を使いましょう
  • docker build --secret id=ca_url,env=UV_DEFAULT_INDEX で環境変数から直接シークレットを渡せます
  • Dockerfile側は RUN --mount=type=secret,id=ca_url,env=PIP_INDEX_URL で環境変数に直接マウントするだけです
  • トークン取得を exec "$@" パターンのラッパースクリプトにしておくと、ビルドだけでなくローカル開発でも使い回せて便利です

Docker Build Secretsはシンプルですが、知らないと ARGENV でトークンを渡してしまいがちです。 プライベートリポジトリを扱う場合は、セキュリティの観点からもぜひ活用してみてください!

三宅陽子(執筆記事の一覧)

アプリケーションサービス部ディベロップメントサービス 1 課

2024年新卒入社です。 アプリケーションサービス部ディベロップメントサービス 1 課に所属しています。