はじめに
こんにちは!三宅です! 最近運動不足でエアロバイクを買いました。
今回は、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の認証トークンを渡す必要があり、トークンをイメージに残さず安全に扱わないといけません。
全体の流れ
今回の構成では、以下の流れでビルドしています。
- トークン取得: 自作スクリプト
codeartifact.shでCodeArtifactの認証トークンを取得し、環境変数にセット - requirements.txt 生成: トークンを使ってCodeArtifactの依存を解決し、
requirements.txtを生成 - Dockerビルド:
docker build --secretでトークンをシークレットとして渡す - 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 ではイメージにトークンが残る
まず思いつくのは ARG や ENV でトークン入りのURLを渡す方法です。
# やってはいけない例 ARG CODEARTIFACT_URL RUN pip install --index-url ${CODEARTIFACT_URL} -r requirements.txt
これでもインストール自体は成功します。しかし、ARGや ENVで渡した値は最終イメージにそれらが残ってしまうため安全ではないです。
プライベートリポジトリの認証トークンがイメージに焼き込まれるのは、セキュリティ上好ましくありません。公式ドキュメントでも、ARG や ENV で機密情報を渡すことは非推奨とされています。
Docker Build Secrets について
Docker Build Secrets は、Dockerのビルド時にパスワードやAPIトークンなどの機密情報を安全に扱うための仕組みです。
ARG や ENV とは違い、ビルド時にだけシークレットを参照でき、最終的なイメージには残りません。
仕組みはシンプルです。
docker buildで--secretを指定してシークレットを渡す- Dockerfile の
RUNで--mount=type=secretを使って一時的にマウントする - 実行中だけ
/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 installはPIP_INDEX_URLを自動的に参照するので、--index-urlオプションも不要です- この
RUNステップが終わると環境変数は消え、イメージのどのレイヤーにもトークンは残りません - ベースイメージを変えれば、Fargate向けでもそのまま同じパターンが使えます
まとめ
ARGやENVでトークンを渡すとイメージのレイヤーに残ってしまうので、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はシンプルですが、知らないと ARG や ENV でトークンを渡してしまいがちです。
プライベートリポジトリを扱う場合は、セキュリティの観点からもぜひ活用してみてください!