EC2上で稼働するDockerコンテナにSecrets Managerの値を連携する方法

業務でEC2 上の Docker コンテナにLaravelを使ったwebアプリケーションを構築する機会があったので、備忘録としてまとめてみました。

まず、EC2 上の Laravelが稼働するDocker コンテナに Secrets Manager の値を安全に引き継ぐ方法としては以下の方法になります。

  1. 毎回 Secrets Manager にアクセスして値を取得
  2. Secrets Manager をコンテナ内で取得しメモリにキャッシュ
  3. 起動時に一度だけ Secrets を取得 → コンテナ内の一時領域に .env を生成

今回はローテーションも特にないため、レスポンス速度を考慮して3の方法で構築しました。

次からは、その手順になります。

1.EC2 にIAM ロールを割り当て

まずは、SecretManagerにアクセスするためのIAM ロールをEC2に割り当てます。
以下は最低限必要なIAMポリシーです。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "GetSpecificSecretOnly",
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:my-laravel-secret-??????"
    }
  ]
}

上記ポリシーを紐づけたロールを、EC2に割り当てます。

2.entrypoint.shで.envを生成する

続いて、SecretManagerから値を取得して.envを更新するためのShellファイルを準備します。


#!/usr/bin/env bash
set -euo pipefail

# ===== デフォルト設定(必要なら環境変数で上書き)=====
REGION="${REGION:-ap-northeast-1}"
SECRET_NAME="${SECRET_NAME:-【Secret名を入れてください】}"
TARGET_ENV_FILE="${TARGET_ENV_FILE:-.env}"

# 取り込むキー
WHITELIST_KEYS=(
【SecretManagerに設定したKeyを記載してください】
)

# ===== Secrets取得 =====
echo "Fetching Secrets Manager: name='${SECRET_NAME}', region='${REGION}' ..."
SECRET_JSON=$(aws secretsmanager get-secret-value \
  --region "${REGION}" \
  --secret-id "${SECRET_NAME}" \
  --query SecretString \
  --output text)

if [[ -z "${SECRET_JSON}" || "${SECRET_JSON}" == "null" ]]; then
  echo "Error: SecretString が空です(SecretBinaryの可能性)。" >&2
  exit 1
fi

# JSON検証
echo "${SECRET_JSON}" | jq . >/dev/null 2>&1 || {
  echo "Error: SecretString が JSON ではありません。" >&2
  exit 1
}

# ===== 出力先の準備(ディレクトリのみ作成)=====
mkdir -p "$(dirname "${TARGET_ENV_FILE}")"

# ===== 既存の .envから、対象キー行を除外したベースを作成 =====
# パターン: ^(KEY1|KEY2|...)=
pattern="^($(IFS='|'; echo "${WHITELIST_KEYS[*]}"))="
tmp_base="$(mktemp)"
if [[ -f "${TARGET_ENV_FILE}" ]]; then
  # 存在する場合のみ grep。該当行を除去した「既存のその他設定」を残す
  grep -vE "${pattern}" "${TARGET_ENV_FILE}" > "${tmp_base}" || true
else
  : > "${tmp_base}"
fi

# ===== Secrets からホワイトリストキーを KEY=VALUE 形式で生成 =====
tmp_secrets="$(mktemp)"
: > "${tmp_secrets}"

for key in "${WHITELIST_KEYS[@]}"; do
  val=$(echo "${SECRET_JSON}" | jq -r --arg k "${key}" '.[$k]')
  if [[ "${val}" == "null" ]]; then
    echo "# ${key} は Secret 内に存在しません" >> "${tmp_secrets}"
    continue
  fi

  # 改行を含む値は \n にエスケープしてダブルクォートで囲む
  if [[ "${val}" =~ [$'\n'] ]]; then
    val="${val//$'\n'/\\n}"
    echo "${key}=\"${val}\"" >> "${tmp_secrets}"
  else
    echo "${key}=${val}" >> "${tmp_secrets}"
  fi
done

# ===== ベース + Secrets を結合して一時ファイルに生成 =====
tmp_new="$(mktemp)"
cat "${tmp_base}" "${tmp_secrets}" > "${tmp_new}"

# ===== 差分がなければ上書きしない(タイムスタンプを無駄に更新しない)=====
if [[ -f "${TARGET_ENV_FILE}" ]] && cmp -s "${tmp_new}" "${TARGET_ENV_FILE}"; then
  echo "No change: ${TARGET_ENV_FILE} は更新不要です。"
  rm -f "${tmp_base}" "${tmp_secrets}" "${tmp_new}"
  exit 0
fi

# ===== mv による入替=====
# パーミッションを既存に合わせる(無ければ 0644)
if [[ -f "${TARGET_ENV_FILE}" ]]; then
  perm=$(stat -c "%a" "${TARGET_ENV_FILE}")
  owner=$(stat -c "%u" "${TARGET_ENV_FILE}")
  group=$(stat -c "%g" "${TARGET_ENV_FILE}")
  install -m "${perm}" -o "${owner}" -g "${group}" "${tmp_new}" "${TARGET_ENV_FILE}"
else
  install -m 0644 "${tmp_new}" "${TARGET_ENV_FILE}"
fi

rm -f "${tmp_base}" "${tmp_secrets}" "${tmp_new}"
echo "Updated ${TARGET_ENV_FILE}"

上記のShellを実行すると、TARGET_ENV_FILEに指定した.envにSecretManagerの値が追加されます。

3.Dockerfileで.envをコンテナ内にコピー

手順2のShellを実行後、生成したenvファイルをコンテナ内にコピーするようなDockerfileを作成して、コンテナを起動するとSecretManagerの値が読まれた状態でコンテナが立ち上がります。

コンテナ起動後は、DBのアクセスなどを試して値が正常に反映されていることを確認してください。