はじめに
iwata-jawsug-jp/devcon は、Web アプリケーションとそのインフラ(IaC)をまとめて開発するためのモノレポテンプレートです。Dev Container を開くだけで、Terraform / AWS CLI / Python / Node / セキュリティスキャンツールが揃った環境ができあがり、バックエンド(FastAPI)・フロントエンド(Vite + Vue 3)・インフラ(Terraform / AWS)を1つのリポジトリで一気通貫に扱えます。
本記事では、公式リポジトリのドキュメント(README / CLAUDE.md / docs/infrastructure.md など)を読み込んだ上で、「まず何をすればいいか」を迷わないように、初期セットアップと使い方を手順として整理しました。
- ローカルで動かすだけ(AWS 不要)の最短ルート
- 自分の GitHub / AWS アカウントに紐づけて、CI/CD で本番デプロイまでする本格セットアップ
の両方を扱います。前者だけであれば AWS の請求は一切発生しません。
devcon とは何か
技術スタック
| 領域 | 技術 |
|---|---|
| バックエンド | Python 3.14 / FastAPI / uv 管理 / SQLAlchemy(async) + Alembic |
| フロントエンド | TypeScript / Vite + Vue 3 / Tailwind CSS v4 / TanStack Query + Pinia |
| データベース | PostgreSQL 16(ローカルは docker-compose、本番は RDS) |
| インフラ | Terraform / AWS(S3 + CloudFront / ECR + ECS / RDS) |
| CI/CD | GitHub Actions(GitHub OIDC で AWS 認証。長期キー不使用) |
| 品質ゲート | pre-commit / ruff / mypy(strict) / eslint / vue-tsc / tflint / trivy / checkov |
| 開発環境 | Dev Container(VS Code)。Claude Code CLI もプリインストール済み |
ディレクトリ構成
.
├── .devcontainer/ # Dev Container 定義(Terraform, AWS CLI, Python 3.14, Node 24, セキュリティツール)
├── .github/workflows/ # CI/CD(ci.yml / cd-infra.yml / cd-app.yml)
├── infra/ # Terraform / AWS の IaC
│ ├── bootstrap/ # 初回のみ・ローカル state(state バケット, OIDC, CI IAM ロール)
│ ├── env/ # 環境ごとの tfvars / backend hcl(*.example のみコミット)
│ └── *.tf # アプリ基盤(web=S3+CloudFront, api=ECR+ECS, shared=VPC ほか)
├── services/
│ ├── backend/python/ # バックエンド REST API(Python / FastAPI / uv 管理)
│ └── frontend/ # フロントエンド SPA(TypeScript / Vite + Vue 3)
├── .pre-commit-config.yaml # fmt / lint / セキュリティスキャンの自動実行
├── .tflint.hcl
└── Makefile # よく使うコマンド集(`make help`)
アーキテクチャの要点
-
frontend(静的 SPA)とbackend(ステートレス JSON API)は別プロセス。ブラウザは相対パス/api/*で API を呼ぶ。 - 開発時は Vite(:5173)が
/api/*を uvicorn(:8000)へプロキシする(ローカルで CORS 設定が不要)。本番は CloudFront が/api/*を API オリジンへルーティングする。コードを変えずにローカルと本番で同じ呼び出し方ができる設計。 - API 契約は FastAPI の OpenAPI(
/openapi.json)。フロントエンドの TypeScript 型はmake gen-typesで自動生成し、リクエスト/レスポンス型を二重管理しない。
使い方1: ローカルで動かすだけ(AWS 不要)
まずはここだけで完結できます。 AWS アカウントも課金も一切不要です。
Step 1. リポジトリを手元に用意する
自分用に使う場合、2通りの方法があります。
方法A: 単純に fork する
一番手早い方法です。GitHub 上で iwata-jawsug-jp/devcon を fork し、clone するだけです。
git clone git@github.com:<あなたのアカウント>/devcon.git
cd devcon
方法B: copier でプロジェクト名を指定して生成する(スキャフォールド)
単に fork するのではなく、プロジェクト名・GitHub org/repo・AWS リージョンを指定して、devcon という文字列がすべて置換された「自分名義の新規プロジェクト」として生成することもできます。
copier copy gh:iwata-jawsug-jp/devcon <生成先ディレクトリ>
対話式で次の変数を聞かれます。
| 変数 | 説明 | 例 |
|---|---|---|
project_name |
プロジェクト名。S3 バケット名・ECR リポジトリ名・Cognito ドメインの一部に使うため、小文字英数字とハイフンのみ、3〜63文字 | my-project |
github_org |
GitHub organization または user 名(infra/bootstrap の OIDC 信頼ポリシーに使う) |
your-org |
github_repo |
GitHub リポジトリ名(省略時は project_name と同じ) |
my-project |
aws_region |
デプロイ先の AWS リージョン | ap-northeast-1 |
生成後にやることは3つです。
-
CODE_OF_CONDUCT.mdの違反報告先メールアドレスを、実在する自分(またはプロジェクトの連絡先)のアドレスに書き換える(機械的に置換されるだけなので実在するとは限らない)。 - 新しい GitHub リポジトリを作成し、生成物を push する。
- 後述の「本格セットアップ」(Git 初期設定 → AWS SSO 設定 →
infra/bootstrapの初回 apply → リポジトリ変数登録)を行う。
なお copier copy で生成したプロジェクトは、git init してコミットしておけば、後から copier update --trust でテンプレート(devcon)側の更新を取り込めます。これが fork との一番の違いです。
Step 2. Dev Container で開く
VS Code でリポジトリを開き、コマンドパレットから 「Reopen in Container」 を実行します。初回はイメージビルドが走るため数分かかります。
ビルドが終わると、コンテナ内で以下のツールがすべて使える状態になります。
terraform / tflint / trivy / checkov / aws / node / python3 / uv / gh / docker
VS Code 拡張機能(Terraform, Python, AWS Toolkit, ESLint, Prettier, Checkov, Trivy, Claude Code など)も自動でインストールされます。
Step 3. セットアップコマンドを実行する
make setup # Python(uv) + Node(npm) の依存関係と pre-commit フックを導入
make dev # backend(:8000) と frontend(:5173) を同時起動
起動したら以下にアクセスできます。
- アプリ本体: http://localhost:5173
- API ドキュメント(Swagger UI): http://localhost:8000/docs
ここまでで「ローカルで動かす」は完了です。 AWS は一切使っていません。
Step 4. コミットする前に Git の identity を設定する
make dev だけなら不要ですが、コードを変更してコミットする場合は先に設定しておきます。
# このリポジトリ限定の identity(--global を付ければ全リポジトリ共通)
git config --local user.name "あなたの名前"
git config --local user.email "<ID>+<ユーザー名>@users.noreply.github.com"
# 推奨デフォルト
git config --local pull.rebase true # pull 時に履歴を直線化
git config --local push.autoSetupRemote true # 初回 push で upstream を自動設定
git config --local fetch.prune true # 削除済みリモートブランチを整理
user.email はメールアドレスを公開しないよう GitHub の noreply メール(<ID>+<ユーザー名>@users.noreply.github.com)を使うことが推奨されています。<ID> は https://api.github.com/users/<ユーザー名> の id フィールドで確認できます。
リモートと認証の確認も忘れずに行います。
git remote -v # origin が設定済みか確認
ssh -T git@github.com # SSH 認証の確認("Hi <user>!" が出れば OK)
git push -u origin main
プライベートメールで push が
GH007エラーで拒否される場合は、noreply メールに切り替えてgit commit --amend --reset-author --no-editで直前コミットの author を書き換えます。ghで PR 操作をする場合は別途gh auth loginを実行してください。
使い方2: 自分の AWS へデプロイする本格セットアップ
ここからは AWS アカウントが必要で、実際にリソースを作成するため課金が発生します。ローカルで動かすだけなら前章までで十分です。
CI/CD(GitHub OIDC → IAM ロール)で自分の AWS へデプロイできるようにするには、一度だけ土台(bootstrap)を作る必要があります。
Step 5. AWS SSO でログインする
tools/script/aws-sso-setup.sh が、AWS SSO プロファイルの作成・ログイン・認証確認を一括で行ってくれます。sso_account_id と SSO start URL は環境ごとに異なるため必須です。
./tools/script/aws-sso-setup.sh -a <sso_account_id> -u <start_url>
# 例: -a 123456789012 -u https://<your-portal>.awsapps.com/start
実行されるのは次の3ステップです。
-
~/.aws/configに[default]プロファイルを書き込む(既存の他プロファイルは保持される) -
aws sso loginでブラウザ認証 -
aws sts get-caller-identityで認証できたか確認
リージョン・ロール名は既定値(ap-northeast-1 / AWSAdministratorAccess)を持っており、変更する場合はオプションで上書きします。
./tools/script/aws-sso-setup.sh --help
./tools/script/aws-sso-setup.sh -a <id> -u <start_url> -p dev -n PowerUserAccess
~/.aws は Dev Container の Docker ボリューム(devcon-aws)で永続化されているため、一度ログインすればコンテナを rebuild してもプロファイルと SSO トークンは残ります(トークン期限切れ時のみ再ログイン)。
IAM Identity Center(旧 AWS SSO)が使えない個人アカウントなどの場合は、代替の一時クレデンシャル発行手順(IAM ユーザー +
get-session-token/assume-role、IAM Roles Anywhere、CloudShell 経由)がdocs/aws-temporary-credentials.mdにまとめられています。
Step 6. infra/bootstrap を初回だけ apply する
state バケット・OIDC プロバイダ・CI 用 IAM ロールを作成します。このステップは一度きりで、実行したマシンでのみ後から update/destroy ができます。
推奨は bootstrap.sh スクリプトを使う方法です。GitHub org/repo・AWS アカウント ID を自動検出し、state バケット名を terraform-<project>-<account_id>-<random6> として自動生成してくれます(同一アカウント内での OIDC プロバイダー重複作成も回避)。
tools/script/bootstrap.sh init -p <project>
tools/script/bootstrap.sh write # 出力をリポジトリ変数へ登録(後述)
tools/script/bootstrap.sh adopt # 別PCで作業を引き継ぐ場合
tools/script/bootstrap.sh recover # state を失った場合の復旧
手動で行う場合(フォールバック)は以下の通りです。
cd infra/bootstrap
terraform init
terraform apply \
-var resource_name_suffix="$(tr -dc 'a-z0-9' </dev/urandom | head -c6)"
terraform output
github_org/github_repoは自分の fork を自動検出しますが、OIDC の信頼関係はこのリポジトリに固定されるため、既定値のままだと fork 先の CD が認証で失敗します。自分のリポジトリ用に明示的に指定してください。
Step 7. bootstrap の出力を GitHub リポジトリ変数に登録する
terraform -chdir=infra/bootstrap output の値を、次の4つの GitHub リポジトリ変数として登録します(gh コマンド例)。
gh variable set AWS_TF_STATE_BUCKET \
--body "$(terraform -chdir=infra/bootstrap output -raw state_bucket_name)"
gh variable set AWS_PLAN_ROLE_ARN \
--body "$(terraform -chdir=infra/bootstrap output -raw ci_plan_role_arn)"
gh variable set AWS_DEPLOY_ROLE_ARN \
--body "$(terraform -chdir=infra/bootstrap output -raw ci_deploy_role_arn)"
gh variable set PROJECT_NAME --body "<bootstrap init -p で指定した project 名>"
tools/script/bootstrap.sh write を使えばこの登録も自動化されます。この4つが未登録の間は cd-infra.yml の plan/apply が OIDC 認証段階で失敗します(想定挙動)。なお agent_mcp_role_arn はリポジトリ変数に登録せず、個人の ~/.aws/config で管理します。
Step 8. infra/env の環境ファイルを用意する
.tfvars / .backend.hcl の実ファイルは git-ignore されているため、example からコピーして作成します。
cp infra/env/dev.backend.hcl.example infra/env/dev.backend.hcl
cp infra/env/dev.tfvars.example infra/env/dev.tfvars
主な穴埋め項目は次の通りです。
dev.backend.hcl
| 項目 | 値 |
|---|---|
bucket |
AWS_TF_STATE_BUCKET の値 |
key |
<project>/<environment>/terraform.tfstate |
region |
ap-northeast-1(既定リージョン) |
use_lockfile |
true(S3 ネイティブロック) |
encrypt |
true |
dev.tfvars
| 項目 | 値 |
|---|---|
project |
PROJECT_NAME |
environment |
dev または prod
|
aws_region |
ap-northeast-1 |
alert_email |
prod は実際のメール、dev は空文字(アラーム疲れ回避) |
RDS インスタンスクラス / ecs_min_capacity / ecs_max_capacity
|
環境に応じて調整 |
アプリ層の Terraform をリモート state で初期化するには、
make tf-init BACKEND=env/dev.backend.hcl
# または
cd infra && terraform init -backend-config=env/dev.backend.hcl
Step 9. CI/CD でデプロイする
-
cd-infra.yml(PR でterraform plan、workflow_dispatchの手動実行でapply)でアプリ基盤(S3 + CloudFront / ECR + ECS / RDS / VPC)を作成します。手動実行時はenvironment入力(dev/prod、既定dev)で対象環境を選び、apply-dev/apply-prodのどちらかのジョブが動きます。 - 続けて
cd-app.yml(prodは無接頭辞、sandbox 環境はSANDBOX_接頭辞)が必要とするリポジトリ変数を登録します。未設定の間はジョブがスキップされます。
以前は terraform output を見ながら gh variable set を12回手で打つ手順でしたが、tools/script/write-cd-app-vars.sh が対象環境の infra/(アプリ層)を apply 済みの前提で自動登録します。
tools/script/write-cd-app-vars.sh prod # cd-app.yml 用(無接頭辞)
tools/script/write-cd-app-vars.sh sandbox # cd-app-sandbox.yml 用(SANDBOX_ 接頭辞)
登録される12変数の一覧・用途は docs/repository-variables.md にまとまっています。
-
cd-app.ymlが backend イメージを ECR へ push して ECS を更新し、frontend を S3 へ同期して CloudFront のキャッシュを無効化します。
日常の開発でよく使うコマンド
make help # 全コマンド一覧
make dev # backend(:8000) + frontend(:5173) を同時起動
make gen-types # API の OpenAPI からフロントエンドの TS 型を生成
make fmt # 全体フォーマット(terraform fmt / ruff / prettier)
make lint # 全体 Lint(tflint / ruff+mypy / eslint+vue-tsc)
make test # 全テスト(pytest / vitest)
make security # Trivy + Checkov で infra をスキャン
サービス単体を直接触りたい場合は以下の通りです。
バックエンド(services/backend/python、FastAPI)
cd services/backend/python
uv sync
uv run uvicorn api.main:app --reload # http://localhost:8000/docs
uv run pytest
フロントエンド(services/frontend、Vite + Vue 3)
cd services/frontend
npm install
npm run dev # Vite dev server(/api は :8000 にプロキシ)
npm test # Vitest(ユニットテスト)
npm run test:e2e # Playwright(E2E テスト)
品質ゲートと CI/CD の仕組み
pre-commit install(= make hooks)を実行すると、コミット時に以下が自動で走ります。
- 汎用: 末尾空白・改行・大容量ファイル・秘密鍵検出
- Terraform:
fmt/validate/tflint/checkov/trivy - Python:
ruff(lint + format) - Node 系:
prettier
.github/workflows/ の GitHub Actions は、Makefile / pre-commit と同じゲートを実行するため「ローカルで green」= 「CI で green」になるよう設計されています。AWS 認証は長期キーを使わず GitHub OIDC で IAM ロールを引き受けます。
| ワークフロー | 役割 |
|---|---|
ci.yml |
PR / main push で変更パスのみ per-service ジョブ(backend / frontend / infra) |
cd-infra.yml |
PR で terraform plan、手動実行(workflow_dispatch)で apply
|
cd-app.yml |
backend イメージを ECR へ push・ECS 更新、frontend を S3 同期 + CloudFront 無効化 |
bootstrap 未適用の状態では、cd-infra.yml の plan/apply は AWS 認証ステップで失敗します(想定挙動)。一方 ci.yml の infra 静的チェック(fmt/validate/tflint/checkov)は AWS 認証不要なので green のままです。
Dev Container 内の永続化と Claude Code
Dev Container を rebuild しても各種ログイン・設定を再構築せずに済むよう、以下が名前付き Docker ボリュームで永続化されています。
| マウント先 | ボリューム | 内容 |
|---|---|---|
~/.aws |
devcon-aws |
AWS SSO プロファイル・トークン |
~/.config/gh |
devcon-gh |
GitHub CLI の認証 |
~/.claude |
devcon-claude |
Claude Code の設定・認証 |
~/.history |
devcon-history |
bash コマンド履歴 |
初回のみボリュームが空のため、各サービスへ一度ログインが必要です。以降は rebuild をまたいで設定が残ります(ボリュームを消すと設定もリセットされます: docker volume rm devcon-*)。
Claude Code CLI と VS Code 拡張は Dev Container にプリインストール済みです。
claude --version # 動作確認
claude # 初回起動時にブラウザでログイン(端末ごとに一度)
Claude 用の設定ファイルは次の3つです。
| ファイル | 用途 | Git |
|---|---|---|
CLAUDE.md |
リポジトリ構成・コマンド・規約を Claude に伝えるガイド | コミットする |
.claude/settings.json |
チーム共有の権限設定(許可/確認/拒否コマンド) | コミットする |
.claude/settings.local.json |
個人用の上書き設定 | コミットしない(.gitignore 済み) |
.claude/settings.json では、安全なコマンド(make / terraform fmt / uv run など)は自動許可、terraform apply・destroy・aws・git push は実行前に確認、秘密情報(*.tfvars / .env / 鍵)の読み取りは拒否する設定になっています。
つまずきやすいポイント(トラブルシューティング)
| 症状 | 原因・対処 |
|---|---|
git push が GH007 で拒否される |
プライベートメールを使っている。noreply メールに切り替えて git commit --amend --reset-author --no-edit
|
fork した状態で cd-infra.yml の CD が認証エラーになる |
infra/bootstrap の OIDC 信頼ポリシーが devcon 本家リポジトリ向けのまま。自分の github_org/github_repo で bootstrap し直す |
cd-infra.yml の plan/apply が OIDC 認証段階で失敗する |
AWS_TF_STATE_BUCKET などの4変数が未登録。Step 7 を実施する(bootstrap 未適用時は想定挙動) |
state ロック取得で AccessDenied になる |
リポジトリ変数 PROJECT_NAME が bootstrap 時の -p <project> と不一致 |
cd-app.yml のジョブがスキップされる |
Step 9 のリポジトリ変数(ECR_REPOSITORY など)が未登録。アプリ基盤未構築の間は正常な挙動 |
まとめ
-
AWS が要らない範囲(fork/生成 → Dev Container →
make setup→make dev)だけでも、型駆動・pre-commit 完備の開発体験を得られます。まずはここから試すのがおすすめです。 -
自分の AWS へ本番デプロイするには、Git identity 設定 → AWS SSO ログイン →
infra/bootstrapの一度きりの apply → リポジトリ変数登録 →cd-infra.yml/cd-app.yml、という順序を守れば迷いません。 - 各ステップの正の情報源は公式ドキュメント(
README.md/CLAUDE.md/docs/infrastructure.md/docs/repository-variables.md/docs/scaffold-cli.md/docs/aws-temporary-credentials.md)です。本記事の内容と齟齬があれば公式ドキュメントを優先してください。
devcon は MIT License で公開されています。少しでも便利だと感じたら、ぜひ GitHub リポジトリ にスターをお願いします。