Amazon EC2 で code-server + Claude Code の開発環境を構築する
はじめに
こんにちは、Paseri です。
Claude Code は CLI やローカル VSCode で使うのが一般的だと思います。
ほかにも、チーム内でエージェントや MCP 等の設定を共有、管理したい場合ブラウザからアクセスできる VSCode(code-server)を EC2 上に構築し、そこで Claude Code を動かすという選択肢もあります。
本記事では、Terraform によるインフラ構築から、Claude Code 拡張機能の導入までをご紹介します。

本記事でやること
- Terraform で EC2 + code-server 環境を構築
- Claude Code のインストールから Amazon Bedrock 経由での利用設定
- Claude Code 拡張機能をインストールして、エディタ上で利用可能にする
Claude Code のセットアップと VSCode の拡張機能については、以下の記事で詳しく紹介しています。
本記事では EC2 + code-server 固有のポイント にフォーカスしてご紹介します。
この構成のメリット・デメリット
EC2 上に code-server を立てて Claude Code を使う構成には、ローカル環境で使う場合と比較して以下のメリットがあります。
- ブラウザだけで使える:端末を選ばずアクセスできるためローカルに VSCode や Node.js のインストールが不要
- 環境を一元管理できる:拡張機能・設定・認証情報をサーバー側で集約、管理できるため利用者ごとの設定で差分が発生しない
- IAM / セキュリティグループでアクセス制御:AWS のセキュリティ機構で誰がいつ使えるかを統制できる。Bedrock の API キーもサーバー側に閉じ込められる
- Bedrock Guardrails と組み合わせやすい:サーバー側で Bedrock を呼び出すため、Guardrails による入出力制御をそのまま適用できる
一方で以下の点は考慮が必要です。
- HTTPS が必須:Claude Code 拡張機能は Service Worker を使うため、証明書が必要
- 拡張機能の制約:code-server は Open VSX を使用するため、Microsoft 公式 Marketplace にしかない拡張は利用できない
- ネットワーク依存:ブラウザからのアクセスとなるため、ネットワーク環境によっては遅延を感じる場合がある
前提条件
本記事の構成を再現するには、以下が事前に必要です。
- Terraform >= 1.0:本構成のデプロイに使用
- AWS CLI 設定済み(SSO ログイン可能な状態):Terraform でのデプロイ時の認証に利用
- Route 53 ホストゾーン(独自ドメイン):Let’s Encrypt での証明書取得に必要
- Bedrock のモデルアクセス有効化:Claude モデルが利用可能な状態にしておく
- Bedrock API キー発行済み:Claude Code の認証に使用(発行手順はこちら)
アーキテクチャ
構成は以下の通りです。

今回はかなり構成を縮小していますが、用途によって適宜変更してください。
| 項目 | 値 |
|---|---|
| OS | Amazon Linux 2023 |
| インスタンスタイプ | t3.small |
| ストレージ | 8 GiB gp3 |
| ネットワーク | 新規 VPC + パブリックサブネット |
| 固定 IP | EIP |
| アクセス制御 | セキュリティグループ で実行元 IP のみ許可(ポート 8080) |
| 認証 | code-server パスワード認証(ランダム生成) |
| 保守接続 | SSM Session Manager |
| バックアップ | AWS Backup(日次、保管 3 日) |
| 自動起動/停止 | EventBridge Scheduler(平日 9:00 起動 / 20:00 停止) |
1. Terraform によるインフラ構築
ファイル構成
今回利用している構成は下記です。(本記事の最下部に添付しています)
terraform/ ├── 01_provider.tf # プロバイダー、backend 設定 ├── 02_variable.tf # 変数定義、パスワード生成 ├── 03_vpc.tf # VPC、サブネット、IGW、ルートテーブル、MyIP 取得 ├── 04_ec2.tf # EC2、EIP、セキュリティグループ、IAM ロール ├── 05_backup.tf # AWS Backup(日次バックアップ) ├── 06_scheduler.tf # EventBridge Scheduler(サーバー自動起動/停止) ├── 07_outputs.tf # URL、パスワード、SSH コマンド等の出力 └── user_data.sh # code-server インストールスクリプト
主要リソースの解説
※ 抜粋です。全文は Appendix を参照ください。
VPC とセキュリティグループ(03_vpc.tf / 04_ec2.tf)
terraform apply 実行時のグローバル IP を自動取得し、セキュリティグループのインバウンドルールに設定します。
これにより、意図しない外部アクセスを防止しています。
# MyIP 取得
data "http" "my_ip" {
url = "https://checkip.amazonaws.com/"
}
locals {
my_ip = "${chomp(data.http.my_ip.response_body)}/32"
}
# セキュリティグループ
resource "aws_security_group" "code_server" {
name = "${var.project_name}-vscode-server-sg"
description = "Security group for code-server"
vpc_id = aws_vpc.this.id
ingress {
description = "code-server from my IP"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = [local.my_ip]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
User Data(user_data.sh)
User Data で code-server のインストールと初期設定を自動化します。
パスワードは random_password で生成して、terraform output で確認できるようにしています。
#!/bin/bash
set -eo pipefail
exec > >(tee /var/log/user-data.log) 2>&1
# ホスト名設定
hostnamectl set-hostname ${hostname}
# パッケージ更新 & インストール
dnf update -y
dnf install -y git tar gzip
# code-server インストール
curl -fsSL https://code-server.dev/install.sh | sh
# code-server 設定ファイル作成
mkdir -p /home/ec2-user/.config/code-server
cat > /home/ec2-user/.config/code-server/config.yaml <<EOF
bind-addr: 0.0.0.0:8080
auth: password
password: ${code_server_password}
cert: false
EOF
# 所有権設定
chown -R ec2-user:ec2-user /home/ec2-user/.config
# systemd サービス有効化・起動
systemctl enable --now code-server@ec2-user
自動起動/停止(06_scheduler.tf)
コスト削減のため、利用しない時間帯は EventBridge Scheduler で平日 9:00〜20:00 のみ起動するスケジュールを設定しています。
# 自動起動 (平日 9:00 JST)
resource "aws_scheduler_schedule" "start" {
name = "${var.project_name}-vscode-server-start"
schedule_expression = "cron(0 9 ? * MON-FRI *)"
schedule_expression_timezone = "Asia/Tokyo"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
role_arn = aws_iam_role.scheduler.arn
input = jsonencode({
InstanceIds = [aws_instance.code_server.id]
})
}
}
# 自動停止 (平日 20:00 JST)
resource "aws_scheduler_schedule" "stop" {
name = "${var.project_name}-vscode-server-stop"
schedule_expression = "cron(0 20 ? * MON-FRI *)"
schedule_expression_timezone = "Asia/Tokyo"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
role_arn = aws_iam_role.scheduler.arn
input = jsonencode({
InstanceIds = [aws_instance.code_server.id]
})
}
}
デプロイ手順
# 1. AWS SSO ログイン aws sso login aws sts get-caller-identity # アカウント確認 # 2. Terraform 実行 cd terraform terraform init terraform plan terraform apply # 3. 接続情報の確認 terraform output code_server_url # アクセス URL terraform output -raw code_server_password # パスワード
ブラウザで表示された URL にアクセスし、パスワードを入力すれば code-server が使えます。
2. HTTPS 化
なぜ HTTPS が必要か
Claude Code 拡張機能は Service Worker を使用しており、Service Worker は HTTPS(または localhost)でしか動作しません。
自己署名証明書でもブロックされるため、正式な証明書が必須です。
今回は、Let’s Encrypt を使って証明書の設定を行います。
DNS 設定
Route53 で A レコードを作成し、EIP に向けます。
| レコード | タイプ | 値 |
|---|---|---|
vscode-server.example.com |
A | <EIP> |
証明書取得
SSM Session Manager で EC2 に接続し、certbot を実行します。
# certbot インストール sudo dnf install -y certbot # 証明書取得(standalone モード) # ※ 一時的に EC2 のセキュリティグループでポート 80 を 0.0.0.0/0 に開放する必要があります sudo certbot certonly --standalone -d vscode-server.example.com
※ 証明書の取得が完了したらセキュリティグループのポート 80 は戻して問題ありません。
code-server に証明書を適用
/home/ec2-user/.config/code-server/config.yaml を編集します。
bind-addr: 0.0.0.0:8080 auth: password password: <パスワード> cert: /etc/letsencrypt/live/vscode-server.example.com/fullchain.pem cert-key: /etc/letsencrypt/live/vscode-server.example.com/privkey.pem
証明書ファイルの権限を設定します。
# ec2-user が読めるよう権限を付与 sudo chmod 755 /etc/letsencrypt/live sudo chmod 755 /etc/letsencrypt/archive sudo chgrp ec2-user /etc/letsencrypt/live/vscode-server.example.com/privkey.pem sudo chmod 640 /etc/letsencrypt/live/vscode-server.example.com/privkey.pem # code-server 再起動 sudo systemctl restart code-server@ec2-user
これで https://vscode-server.example.com:8080 でアクセスできるようになります。
3. Claude Code 拡張機能の導入
Open VSX からインストール
code-server は Microsoft 公式の VSCode Marketplace ではなく、Open VSX(Eclipse Foundation 運営)を使用しています。
これはライセンス上の制約によるものです。
Claude Code 拡張機能は Open VSX にも公開されているため、code-server の拡張機能画面から「Claude」で検索してインストールできます。

認証設定(環境変数方式)
code-server は systemd 経由で起動するため、.bashrc の環境変数は読み込まれません。
systemd の override ファイルで設定します。
# override ファイル作成 sudo mkdir -p /etc/systemd/system/code-server@ec2-user.service.d sudo tee /etc/systemd/system/code-server@ec2-user.service.d/env.conf <<EOF [Service] Environment="AWS_BEARER_TOKEN_BEDROCK=<Bedrock API キー>" Environment="CLAUDE_CODE_USE_BEDROCK=1" Environment="AWS_REGION=ap-northeast-1" EOF # 反映 sudo systemctl daemon-reload sudo systemctl restart code-server@ec2-user
| 環境変数 | 説明 |
|---|---|
AWS_BEARER_TOKEN_BEDROCK |
Bedrock の API キー |
CLAUDE_CODE_USE_BEDROCK |
Bedrock 経由を有効化 |
AWS_REGION |
Bedrock を利用するリージョン |
Claude Code の細かい設定についてはこちらの記事を参照してください。
動作確認
拡張機能のインストールと環境変数の設定が完了すると、VSCode での利用と同じようにサイドバーから Claude Code が利用可能になります。

注意事項
セキュリティグループの IP 制限
セキュリティグループは terraform apply 実行時の IP のみ許可します。
アクセス元の IP が変わる場合は都度修正をする必要があります。
証明書の更新
Let’s Encrypt の証明書は 90 日間有効です。期限が近づいたら以下の手順で更新します。
1:セキュリティグループでポート 80 を 0.0.0.0/0 に一時開放
2:SSM で接続し、以下を実行:
sudo systemctl stop code-server@ec2-user sudo certbot renew sudo systemctl start code-server@ec2-user
3:セキュリティグループのポート 80 ルールを削除
まとめ
今回は EC2 上で構築した、code-server に Claude Code を導入する方法をご紹介しました。
これにより、Claude Code の設定をサーバー上で統一できるメリットを受けつつ VSCode 互換エディタでの利用ができるようになりました。
要点をまとめると:
- Terraform で一発構築 ─ VPC、EC2、バックアップ、自動起動/停止まで IaC で管理
- HTTPS は必須 ─ Claude Code 拡張機能の Service Worker が動作するために正式な証明書が必要
- systemd override で環境変数を設定 ─
.bashrcは systemd 起動では読まれないため
少しでも参考になれば幸いです!
最後までお読み頂きありがとうございました!
Appendix
01_provider.tf
# -----------------------
# Terraform configuration
# -----------------------
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
http = {
source = "hashicorp/http"
version = "~> 3.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
backend "s3" {
bucket = "your-terraform-state-bucket"
key = "vscode-server/terraform.tfstate"
region = "ap-northeast-1"
}
}
# -----------------------
# Provider
# -----------------------
provider "aws" {
region = var.aws_region
default_tags {
tags = {
ManagedBy = "Terraform"
Project = var.project_name
}
}
}
02_variable.tf
################################################################################
# code-server パスワード生成
################################################################################
resource "random_password" "code_server" {
length = 16
special = false
}
################################################################################
# 変数
################################################################################
variable "aws_region" {
description = "AWS リージョン"
type = string
default = "ap-northeast-1"
}
variable "project_name" {
description = "プロジェクト名"
type = string
default = "code-server"
}
03_vpc.tf
################################################################################
# MyIP 取得
################################################################################
data "http" "my_ip" {
url = "https://checkip.amazonaws.com/"
}
locals {
my_ip = "${chomp(data.http.my_ip.response_body)}/32"
}
################################################################################
# VPC
################################################################################
resource "aws_vpc" "this" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "${var.project_name}-vscode-server-vpc"
}
}
################################################################################
# パブリックサブネット
################################################################################
resource "aws_subnet" "public" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-vscode-server-public-subnet"
}
}
################################################################################
# インターネットゲートウェイ
################################################################################
resource "aws_internet_gateway" "this" {
vpc_id = aws_vpc.this.id
tags = {
Name = "${var.project_name}-vscode-server-igw"
}
}
################################################################################
# ルートテーブル
################################################################################
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.this.id
}
tags = {
Name = "${var.project_name}-vscode-server-public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.public.id
route_table_id = aws_route_table.public.id
}
04_ec2.tf
################################################################################
# AMI (Amazon Linux 2023 最新)
################################################################################
data "aws_ami" "al2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-2023.*-kernel-6.1-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
################################################################################
# IAM ロール (SSM 用)
################################################################################
resource "aws_iam_role" "ec2" {
name = "${var.project_name}-vscode-server-ec2-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-vscode-server-ec2-role"
}
}
resource "aws_iam_role_policy_attachment" "ssm" {
role = aws_iam_role.ec2.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_instance_profile" "ec2" {
name = "${var.project_name}-vscode-server-ec2-profile"
role = aws_iam_role.ec2.name
}
################################################################################
# セキュリティグループ
################################################################################
resource "aws_security_group" "code_server" {
name = "${var.project_name}-vscode-server-sg"
description = "Security group for code-server"
vpc_id = aws_vpc.this.id
# code-server (HTTP)
ingress {
description = "code-server from my IP"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = [local.my_ip]
}
# アウトバウンド全許可
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-vscode-server-sg"
}
}
################################################################################
# EC2 インスタンス
################################################################################
resource "aws_instance" "code_server" {
ami = data.aws_ami.al2023.id
instance_type = "t3.small"
subnet_id = aws_subnet.public.id
vpc_security_group_ids = [aws_security_group.code_server.id]
iam_instance_profile = aws_iam_instance_profile.ec2.name
root_block_device {
volume_size = 8
volume_type = "gp3"
encrypted = true
}
user_data = templatefile("${path.module}/user_data.sh", {
code_server_password = random_password.code_server.result
hostname = "${var.project_name}-vscode-server-ec2"
})
tags = {
Name = "${var.project_name}-vscode-server-ec2"
}
}
################################################################################
# EIP
################################################################################
resource "aws_eip" "code_server" {
instance = aws_instance.code_server.id
domain = "vpc"
tags = {
Name = "${var.project_name}-vscode-server-eip"
}
}
05_backup.tf
################################################################################
# AWS Backup Vault
################################################################################
resource "aws_backup_vault" "this" {
name = "${var.project_name}-vscode-server-vault"
tags = {
Name = "${var.project_name}-vscode-server-vault"
}
}
################################################################################
# AWS Backup Plan (1日1回、保管3日間)
################################################################################
resource "aws_backup_plan" "this" {
name = "${var.project_name}-vscode-server-backup-plan"
rule {
rule_name = "daily-backup"
target_vault_name = aws_backup_vault.this.name
schedule = "cron(0 18 * * ? *)"
lifecycle {
delete_after = 3
}
}
tags = {
Name = "${var.project_name}-vscode-server-backup-plan"
}
}
################################################################################
# AWS Backup IAM ロール
################################################################################
resource "aws_iam_role" "backup" {
name = "${var.project_name}-vscode-server-backup-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "backup.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-vscode-server-backup-role"
}
}
resource "aws_iam_role_policy_attachment" "backup" {
role = aws_iam_role.backup.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSBackupServiceRolePolicyForBackup"
}
################################################################################
# AWS Backup Selection (EC2 インスタンス)
################################################################################
resource "aws_backup_selection" "this" {
name = "${var.project_name}-vscode-server-backup-selection"
plan_id = aws_backup_plan.this.id
iam_role_arn = aws_iam_role.backup.arn
resources = [
aws_instance.code_server.arn
]
}
06_scheduler.tf
################################################################################
# EC2 自動起動/停止用 IAM ロール (EventBridge Scheduler)
################################################################################
resource "aws_iam_role" "scheduler" {
name = "${var.project_name}-vscode-server-scheduler-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "scheduler.amazonaws.com"
}
}
]
})
tags = {
Name = "${var.project_name}-vscode-server-scheduler-role"
}
}
resource "aws_iam_role_policy" "scheduler" {
name = "${var.project_name}-vscode-server-scheduler-policy"
role = aws_iam_role.scheduler.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ec2:StartInstances",
"ec2:StopInstances"
]
Resource = aws_instance.code_server.arn
}
]
})
}
################################################################################
# 自動起動 (平日 9:00 JST)
################################################################################
resource "aws_scheduler_schedule" "start" {
name = "${var.project_name}-vscode-server-start"
group_name = "default"
schedule_expression = "cron(0 9 ? * MON-FRI *)"
schedule_expression_timezone = "Asia/Tokyo"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:ec2:startInstances"
role_arn = aws_iam_role.scheduler.arn
input = jsonencode({
InstanceIds = [aws_instance.code_server.id]
})
}
}
################################################################################
# 自動停止 (平日 20:00 JST)
################################################################################
resource "aws_scheduler_schedule" "stop" {
name = "${var.project_name}-vscode-server-stop"
group_name = "default"
schedule_expression = "cron(0 20 ? * MON-FRI *)"
schedule_expression_timezone = "Asia/Tokyo"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:ec2:stopInstances"
role_arn = aws_iam_role.scheduler.arn
input = jsonencode({
InstanceIds = [aws_instance.code_server.id]
})
}
}
07_outputs.tf
output "code_server_url" {
description = "code-server アクセス URL"
value = "http://${aws_eip.code_server.public_ip}:8080"
}
output "code_server_password" {
description = "code-server ログインパスワード"
value = random_password.code_server.result
sensitive = true
}
output "ssh_command" {
description = "SSH 接続コマンド"
value = "ssh -i <秘密鍵パス> ec2-user@${aws_eip.code_server.public_ip}"
}
output "instance_id" {
description = "EC2 インスタンス ID"
value = aws_instance.code_server.id
}
output "my_ip" {
description = "セキュリティグループに許可された IP"
value = local.my_ip
}
user_data.sh
#!/bin/bash
set -eo pipefail
# ログ出力
exec > >(tee /var/log/user-data.log) 2>&1
echo "=== user_data start: $(date) ==="
# HOME を明示的に設定
export HOME=/root
# ホスト名設定
hostnamectl set-hostname ${hostname}
# パッケージ更新
dnf update -y
# 必要パッケージインストール
dnf install -y git tar gzip
# code-server インストール
curl -fsSL https://code-server.dev/install.sh | sh
# code-server 設定ファイル作成
mkdir -p /home/ec2-user/.config/code-server
cat > /home/ec2-user/.config/code-server/config.yaml <<EOF
bind-addr: 0.0.0.0:8080
auth: password
password: ${code_server_password}
cert: false
EOF
# 所有権設定
chown -R ec2-user:ec2-user /home/ec2-user/.config
# systemd サービス有効化・起動
systemctl enable --now code-server@ec2-user
echo "=== user_data complete: $(date) ==="
2024年新卒入社。うどん好きな初心者クラウドエンジニア。
Recommends
こちらもおすすめ
Special Topics
注目記事はこちら
データ分析入門
これから始めるBigQuery基礎知識
2024.02.28

AWSの料金が 10 %割引になる!
『AWSの請求代行リセールサービス』
2024.07.16

