I tried adding CloudFront to a browser-only Claude Code environment (EC2 + code-server + Bedrock) and making it possible to install extensions too

I tried adding CloudFront to a browser-only Claude Code environment (EC2 + code-server + Bedrock) and making it possible to install extensions too

In the previously built EC2 + code-server + Bedrock environment, I placed CloudFront in front of it, resolving the browser warnings caused by self-signed certificates and the extension installation restrictions. Since you can now use extensions without being troubled by certificate errors, it becomes an environment that is easy to distribute as-is for training sessions and hands-on workshops.
2026.08.25

This page has been translated by machine translation. View original

Hello, I'm Hayashi.

Last time, I built an environment running Claude Code via Bedrock on code-server hosted on EC2.

https://dev.classmethod.jp/articles/202608-ec2-code-server-claude-code-bedrock/

That configuration had some limitations. Because code-server runs HTTPS using its own self-signed certificate, browsers display warnings and extension installation also fails.

This time, I placed CloudFront in front of EC2 to resolve these limitations.
Since CloudFront's default domain (xxxxxxxx.cloudfront.net) comes with a browser-trusted certificate from the start, there is no need to prepare your own certificate.

Configuration

Configuration diagram

The difference from last time is that CloudFront is placed in front of EC2, and TLS termination moves to the CloudFront side.
code-server on EC2 runs over unencrypted HTTP, and direct access to EC2 from the internet is blocked.

The VPC and IAM role are the same as last time.
Since this article only covers the changes, please refer to the previous article for the prerequisites (enabling Bedrock model access, creating an IAM role, and creating a VPC).

Modifying the EC2 Security Group

Last time, TCP port 50443 for code-server was allowed only from my own source IP.
This time, I remove that rule and instead allow TCP port 8080 only from the CloudFront managed prefix list.

Type Port Range Source
Custom TCP 8080 com.amazonaws.global.cloudfront.origin-facing (prefix list)

The ID you actually specify for the source is in the format pl-xxxxxxxx and differs by region. In the inbound rule editing screen of the security group, if you type com.amazonaws.global.cloudfront.origin-facing in the source field, it will appear as a suggestion, and selecting it will automatically fill in the ID for that region.

Since this ID differs by region, the CloudFormation template described later uses Mappings to provide a region-by-region lookup table.

Even with CloudFront placed in front, leaving EC2 directly accessible defeats the purpose.
By restricting the route to the origin to CloudFront only, it becomes unreachable from the internet even though it listens over unencrypted HTTP.

Modifying the code-server Configuration

Since TLS is terminated on the CloudFront side, code-server on EC2 runs over unencrypted HTTP.

Of the config.yaml created in the user data, only the following two lines change.

bind-addr: 0.0.0.0:8080
cert: false

Last time it was bind-addr: 0.0.0.0:50443 and cert: true.
The Claude Code installation part (such as creating ~/.claude/settings.json) is exactly the same as last time.

Adding an EIP and CloudFront Distribution

The EC2 public DNS name is specified as the CloudFront origin.
Since this DNS name changes when EC2 is stopped and started, you first associate an EIP to fix the DNS name, then create the distribution.

Allocate the EIP from Elastic IP in the EC2 console and associate it with the target EC2 instance.
This ensures that the public IP and public DNS name do not change even when the instance is stopped and started.

The main settings for the distribution are as follows.

Item Setting
Origin domain EC2 public DNS name
Origin protocol policy HTTP only
HTTP port 8080
Viewer protocol policy Redirect HTTP to HTTPS
Allowed HTTP methods GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
Cache policy CachingDisabled
Origin request policy AllViewer

Both CachingDisabled and AllViewer are CloudFront managed policies.
Since code-server is a dynamic application whose responses change with every operation, caching is disabled, and AllViewer forwards all headers, cookies, and query strings to the origin. Note that restricting HTTP methods to only GET/HEAD will prevent file saving and extension installation.

In the CloudFormation template described later, these policies are specified by ID.
Since CloudFront is a global service with no region, these IDs are fixed regardless of region or account, unlike prefix list IDs.


Automated Setup with CloudFormation

This is the CloudFormation template reflecting all the changes made so far.

Ubuntu 24.04 Full Template
code-server-cloudfront-ubuntu.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: code-server + Claude Code (Amazon Bedrock) behind CloudFront on Ubuntu 24.04
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Set basic information.
        Parameters:
          - SystemName
      - Label:
          default: Set your EC2 instance settings.
        Parameters:
          - InstanceType
          - AMIID
Parameters:
  SystemName:
    Description: "Prefix of each resource name."
    Type: String
    Default: "code-server"
  InstanceType:
    Description: "EC2 instance type."
    Type: String
    Default: "t3.medium"
  AMIID:
    Description: "Ubuntu 24.04 AMI ID."
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/canonical/ubuntu/server/24.04/stable/current/amd64/hvm/ebs-gp3/ami-id
Mappings:
  # CloudFront managed prefix list ID (per region)
  # Reference: https://aws.amazon.com/jp/blogs/news/limit-access-to-your-origins-using-the-aws-managed-prefix-list-for-amazon-cloudfront/
  AWSRegions2PrefixListID:
    ap-northeast-1:
      PrefixList: pl-58a04531
    ap-northeast-2:
      PrefixList: pl-22a6434b
    ap-northeast-3:
      PrefixList: pl-31a14458
    ap-south-1:
      PrefixList: pl-9aa247f3
    ap-southeast-1:
      PrefixList: pl-31a34658
    ap-southeast-2:
      PrefixList: pl-b8a742d1
    ca-central-1:
      PrefixList: pl-38a64351
    eu-central-1:
      PrefixList: pl-a3a144ca
    eu-north-1:
      PrefixList: pl-fab65393
    eu-west-1:
      PrefixList: pl-4fa04526
    eu-west-2:
      PrefixList: pl-93a247fa
    eu-west-3:
      PrefixList: pl-75b1541c
    sa-east-1:
      PrefixList: pl-5da64334
    us-east-1:
      PrefixList: pl-3b927c52
    us-east-2:
      PrefixList: pl-b6a144df
    us-west-1:
      PrefixList: pl-4ea04527
    us-west-2:
      PrefixList: pl-82a045eb
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-vpc"
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-igw"
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [0, !GetAZs ""]
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-public-subnet"
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-public-rtb"
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PublicRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet

  CodeServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${SystemName}-sg"
      GroupDescription: "Security group for code-server (HTTP on 8080, from CloudFront only)"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          SourcePrefixListId: !FindInMap [AWSRegions2PrefixListID, !Ref "AWS::Region", PrefixList]
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-sg"

  CodeServerRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub
        - "${SystemName}-role-${Suffix}"
        - Suffix: !Select [4, !Split ["-", !Select [2, !Split ["/", !Ref "AWS::StackId"]]]]
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
      Policies:
        - PolicyName: "BedrockInvokeAnthropicModels"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: "BedrockInvokeAnthropicModels"
                Effect: "Allow"
                Action:
                  - "bedrock:InvokeModel"
                  - "bedrock:InvokeModelWithResponseStream"
                Resource:
                  - "arn:aws:bedrock:*::foundation-model/anthropic.*"
                  - !Sub "arn:aws:bedrock:*:${AWS::AccountId}:inference-profile/*.anthropic.*"
  CodeServerInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref CodeServerRole

  CodeServerEC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref AMIID
      InstanceType: !Ref InstanceType
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            Encrypted: true
            VolumeSize: 16
            VolumeType: gp3
            DeleteOnTermination: true
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref CodeServerSecurityGroup
      IamInstanceProfile: !Ref CodeServerInstanceProfile
      Tags:
        - Key: Name
          Value: !Ref SystemName
      UserData:
        Fn::Base64: |
          #!/bin/bash
          set -eux

          # Install code-server
          apt update
          apt install -y jq curl openssl

          CODER_VERSION=$(curl -s https://api.github.com/repos/coder/code-server/releases/latest | jq -r .tag_name | sed 's/v//')
          curl -fOL "https://github.com/coder/code-server/releases/download/v${CODER_VERSION}/code-server_${CODER_VERSION}_amd64.deb"
          apt install -y ./code-server_${CODER_VERSION}_amd64.deb
          rm -f code-server_${CODER_VERSION}_amd64.deb

          mkdir -p /home/ubuntu/.config/code-server/
          cat > /home/ubuntu/.config/code-server/config.yaml << EOF
          bind-addr: 0.0.0.0:8080
          auth: password
          password: $(openssl rand -hex 16)
          cert: false
          EOF
          chown -R ubuntu:ubuntu /home/ubuntu/.config
          systemctl enable --now code-server@ubuntu

          # Install Claude Code
          sudo -u ubuntu bash -c 'curl -fsSL https://claude.ai/install.sh | bash'

          mkdir -p /home/ubuntu/.claude
          cat > /home/ubuntu/.claude/settings.json << 'EOF'
          {
            "env": {
              "CLAUDE_CODE_USE_BEDROCK": "1",
              "AWS_REGION": "ap-northeast-1",
              "ANTHROPIC_MODEL": "jp.anthropic.claude-sonnet-4-6"
            }
          }
          EOF
          chown -R ubuntu:ubuntu /home/ubuntu/.claude

          # Add PATH
          sudo -u ubuntu bash -c 'grep -q ".local/bin" /home/ubuntu/.bashrc || echo "export PATH=\"$HOME/.local/bin:\$PATH\"" >> /home/ubuntu/.bashrc'

  CodeServerEIP:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref CodeServerEC2
      Tags:
        - Key: Name
          Value: !Ref SystemName

  CodeServerDistribution:
    Type: AWS::CloudFront::Distribution
    DependsOn: CodeServerEIP
    Properties:
      DistributionConfig:
        Enabled: true
        Comment: !Ref SystemName
        Origins:
          - Id: "code-server"
            DomainName: !GetAtt CodeServerEC2.PublicDnsName
            CustomOriginConfig:
              OriginProtocolPolicy: "http-only"
              HTTPPort: 8080
        DefaultCacheBehavior:
          TargetOriginId: "code-server"
          ViewerProtocolPolicy: "redirect-to-https"
          AllowedMethods:
            - "GET"
            - "HEAD"
            - "OPTIONS"
            - "PUT"
            - "POST"
            - "PATCH"
            - "DELETE"
          # CachingDisabled
          CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad"
          # AllViewer
          OriginRequestPolicyId: "216adef6-5c7f-47e4-b989-5492eafa07d3"

Outputs:
  CloudFrontDomain:
    Description: "code-server URL. Access it at https://<CloudFrontDomain>"
    Value: !GetAtt CodeServerDistribution.DomainName
Amazon Linux 2023 Full Template
code-server-cloudfront-al2023.yaml
AWSTemplateFormatVersion: 2010-09-09
Description: code-server + Claude Code (Amazon Bedrock) behind CloudFront on Amazon Linux 2023
Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Set basic information.
        Parameters:
          - SystemName
      - Label:
          default: Set your EC2 instance settings.
        Parameters:
          - InstanceType
          - AMIID
Parameters:
  SystemName:
    Description: "Prefix of each resource name."
    Type: String
    Default: "code-server"
  InstanceType:
    Description: "EC2 instance type."
    Type: String
    Default: "t3.medium"
  AMIID:
    Description: "Amazon Linux 2023 AMI ID."
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64
Mappings:
  # CloudFront managed prefix list ID (per region)
  # Reference: https://aws.amazon.com/jp/blogs/news/limit-access-to-your-origins-using-the-aws-managed-prefix-list-for-amazon-cloudfront/
  AWSRegions2PrefixListID:
    ap-northeast-1:
      PrefixList: pl-58a04531
    ap-northeast-2:
      PrefixList: pl-22a6434b
    ap-northeast-3:
      PrefixList: pl-31a14458
    ap-south-1:
      PrefixList: pl-9aa247f3
    ap-southeast-1:
      PrefixList: pl-31a34658
    ap-southeast-2:
      PrefixList: pl-b8a742d1
    ca-central-1:
      PrefixList: pl-38a64351
    eu-central-1:
      PrefixList: pl-a3a144ca
    eu-north-1:
      PrefixList: pl-fab65393
    eu-west-1:
      PrefixList: pl-4fa04526
    eu-west-2:
      PrefixList: pl-93a247fa
    eu-west-3:
      PrefixList: pl-75b1541c
    sa-east-1:
      PrefixList: pl-5da64334
    us-east-1:
      PrefixList: pl-3b927c52
    us-east-2:
      PrefixList: pl-b6a144df
    us-west-1:
      PrefixList: pl-4ea04527
    us-west-2:
      PrefixList: pl-82a045eb
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-vpc"
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-igw"
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      AvailabilityZone: !Select [0, !GetAZs ""]
      VpcId: !Ref VPC
      CidrBlock: 10.0.1.0/24
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-public-subnet"
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-public-rtb"
  PublicRoute:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  PublicRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet

  CodeServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub "${SystemName}-sg"
      GroupDescription: "Security group for code-server (HTTP on 8080, from CloudFront only)"
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          SourcePrefixListId: !FindInMap [AWSRegions2PrefixListID, !Ref "AWS::Region", PrefixList]
      Tags:
        - Key: Name
          Value: !Sub "${SystemName}-sg"

  CodeServerRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub
        - "${SystemName}-role-${Suffix}"
        - Suffix: !Select [4, !Split ["-", !Select [2, !Split ["/", !Ref "AWS::StackId"]]]]
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
      Policies:
        - PolicyName: "BedrockInvokeAnthropicModels"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Sid: "BedrockInvokeAnthropicModels"
                Effect: "Allow"
                Action:
                  - "bedrock:InvokeModel"
                  - "bedrock:InvokeModelWithResponseStream"
                Resource:
                  - "arn:aws:bedrock:*::foundation-model/anthropic.*"
                  - !Sub "arn:aws:bedrock:*:${AWS::AccountId}:inference-profile/*.anthropic.*"
  CodeServerInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref CodeServerRole

  CodeServerEC2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref AMIID
      InstanceType: !Ref InstanceType
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            Encrypted: true
            VolumeSize: 16
            VolumeType: gp3
            DeleteOnTermination: true
      NetworkInterfaces:
        - AssociatePublicIpAddress: true
          DeviceIndex: "0"
          SubnetId: !Ref PublicSubnet
          GroupSet:
            - !Ref CodeServerSecurityGroup
      IamInstanceProfile: !Ref CodeServerInstanceProfile
      Tags:
        - Key: Name
          Value: !Ref SystemName
      UserData:
        Fn::Base64: |
          #!/bin/bash
          set -eux

          # Install code-server
          dnf install -y jq openssl

          CODER_VERSION=$(curl -s https://api.github.com/repos/coder/code-server/releases/latest | jq -r .tag_name | sed 's/v//')
          curl -fOL "https://github.com/coder/code-server/releases/download/v${CODER_VERSION}/code-server-${CODER_VERSION}-amd64.rpm"
          dnf install -y ./code-server-${CODER_VERSION}-amd64.rpm
          rm -f code-server-${CODER_VERSION}-amd64.rpm

          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: $(openssl rand -hex 16)
          cert: false
          EOF
          chown -R ec2-user:ec2-user /home/ec2-user/.config
          systemctl enable --now code-server@ec2-user

          # Install Claude Code
          sudo -u ec2-user bash -c 'curl -fsSL https://claude.ai/install.sh | bash'

          mkdir -p /home/ec2-user/.claude
          cat > /home/ec2-user/.claude/settings.json << 'EOF'
          {
            "env": {
              "CLAUDE_CODE_USE_BEDROCK": "1",
              "AWS_REGION": "ap-northeast-1",
              "ANTHROPIC_MODEL": "jp.anthropic.claude-sonnet-4-6"
            }
          }
          EOF
          chown -R ec2-user:ec2-user /home/ec2-user/.claude

          # Add PATH
          sudo -u ec2-user bash -c 'grep -q ".local/bin" /home/ec2-user/.bashrc || echo "export PATH=\"$HOME/.local/bin:\$PATH\"" >> /home/ec2-user/.bashrc'

  CodeServerEIP:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref CodeServerEC2
      Tags:
        - Key: Name
          Value: !Ref SystemName

  CodeServerDistribution:
    Type: AWS::CloudFront::Distribution
    DependsOn: CodeServerEIP
    Properties:
      DistributionConfig:
        Enabled: true
        Comment: !Ref SystemName
        Origins:
          - Id: "code-server"
            DomainName: !GetAtt CodeServerEC2.PublicDnsName
            CustomOriginConfig:
              OriginProtocolPolicy: "http-only"
              HTTPPort: 8080
        DefaultCacheBehavior:
          TargetOriginId: "code-server"
          ViewerProtocolPolicy: "redirect-to-https"
          AllowedMethods:
            - "GET"
            - "HEAD"
            - "OPTIONS"
            - "PUT"
            - "POST"
            - "PATCH"
            - "DELETE"
          # CachingDisabled
          CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad"
          # AllViewer
          OriginRequestPolicyId: "216adef6-5c7f-47e4-b989-5492eafa07d3"

Outputs:
  CloudFrontDomain:
    Description: "code-server URL. Access it at https://<CloudFrontDomain>"
    Value: !GetAtt CodeServerDistribution.DomainName

The VPC and IAM role sections are identical to the previous version. The differences in the template are the following 7 points.

  • Removed the AllowedIP parameter
    Since access to EC2 is now limited to CloudFront, there is no longer a need to specify your own IP.
- AllowedIP:
-   Description: "Your access source IP in CIDR notation (e.g. 203.0.113.10/32)."
-   Type: String
  • Added Mappings to look up the CloudFront managed prefix list ID per region
+ Mappings:
+   AWSRegions2PrefixListID:
+     ap-northeast-1:
+       PrefixList: pl-58a04531
+     ap-northeast-2:
+       PrefixList: pl-22a6434b
+     # ...(17 regions in total. See the full template below for all entries)
  • CodeServerSecurityGroup has changes to the port and allowed source
        SecurityGroupIngress:
          - IpProtocol: tcp
-           FromPort: 50443
-           ToPort: 50443
-           CidrIp: !Ref AllowedIP
+           FromPort: 8080
+           ToPort: 8080
+           SourcePrefixListId: !FindInMap [AWSRegions2PrefixListID, !Ref "AWS::Region", PrefixList]
  • The part of CodeServerEC2 UserData that generates config.yaml also changes
          cat > /home/ubuntu/.config/code-server/config.yaml << EOF
-         bind-addr: 0.0.0.0:50443
+         bind-addr: 0.0.0.0:8080
          auth: password
          password: $(openssl rand -hex 16)
-         cert: true
+         cert: false
          EOF
  • CodeServerEIP and CodeServerDistribution are newly added resources
  CodeServerEIP:
    Type: AWS::EC2::EIP
    Properties:
      InstanceId: !Ref CodeServerEC2
      Tags:
        - Key: Name
          Value: !Ref SystemName

  CodeServerDistribution:
    Type: AWS::CloudFront::Distribution
    DependsOn: CodeServerEIP
    Properties:
      DistributionConfig:
        Enabled: true
        Comment: !Ref SystemName
        Origins:
          - Id: "code-server"
            DomainName: !GetAtt CodeServerEC2.PublicDnsName
            CustomOriginConfig:
              OriginProtocolPolicy: "http-only"
              HTTPPort: 8080
        DefaultCacheBehavior:
          TargetOriginId: "code-server"
          ViewerProtocolPolicy: "redirect-to-https"
          AllowedMethods:
            - "GET"
            - "HEAD"
            - "OPTIONS"
            - "PUT"
            - "POST"
            - "PATCH"
            - "DELETE"
          # CachingDisabled
          CachePolicyId: "4135ea2d-6df8-44a3-9df3-4b5a84be39ad"
          # AllViewer
          OriginRequestPolicyId: "216adef6-5c7f-47e4-b989-5492eafa07d3"
  • Changed Outputs from the EC2 public IP to the CloudFront domain.
- PublicIP:
-   Description: "code-server public IP. Access it at https://<PublicIP>:50443"
-   Value: !GetAtt CodeServerEC2.PublicIp
+ CloudFrontDomain:
+   Description: "code-server URL. Access it at https://<CloudFrontDomain>"
+   Value: !GetAtt CodeServerDistribution.DomainName

Deployment and Operation Verification

The deployment command is as follows.
Other than the removal of AllowedIP, it is the same format as last time.

aws cloudformation create-stack --stack-name temp-code-server-cloudfront \
    --template-body file://./code-server-cloudfront-ubuntu.yaml \
    --capabilities CAPABILITY_NAMED_IAM

For the Amazon Linux 2023 version, change --template-body to code-server-cloudfront-al2023.yaml.
If you want to change the model being used, rewrite ANTHROPIC_MODEL in ~/.claude/settings.json inside the template's user data.

When deployment is complete, the CloudFront domain (xxxxxxxx.cloudfront.net) will be output in the Outputs. Since creating a distribution takes a few minutes, please wait a moment for it to become active before proceeding.

The method for checking the password via Session Manager is the same as last time.

# For Ubuntu
sudo cat /home/ubuntu/.config/code-server/config.yaml | grep password:

# For Amazon Linux 2023
sudo cat /home/ec2-user/.config/code-server/config.yaml | grep password:

Access the CloudFront domain in your browser. There are two differences from last time.

First, no certificate warning will appear. Since we are using CloudFront's default domain, you can access it with a certificate trusted by the browser.

Screenshot showing the code-server login screen opening without a certificate warning

And the main point — whether extensions can be installed. I tried installing two extensions: "Japanese Language Pack for Visual Studio Code" for Japanese localization, and "Claude Code for VS Code".

Installation result of Japanese Language Pack for Visual Studio Code

Installation result of Claude Code for VS Code

Both installed without any issues. The Japanese language pack will take effect by following the restart prompt that appears after installation. I was also able to confirm that the menus are actually displayed in Japanese.

Screen showing an attempt to open a terminal with the menus localized in Japanese

I also verified that the terminal and file saving work properly through CloudFront. Since code-server communicates via WebSocket, if this does not work, the editor cannot be used.

Screenshot showing claude auth status executed in the terminal, confirming login via Bedrock

Both launching the terminal and executing commands worked without issues, and I was able to confirm the Bedrock login status with claude auth status.

Operational Notes

With the addition of CloudFront and EIP, costs will be higher than the previous configuration.
EIPs are subject to charges even when attached to an instance, so please delete the entire stack when you are done.
Although CloudFront's default domain is a hard-to-guess string, anyone who knows the URL can access it. Since authentication relies solely on the code-server password, please consider adding Cognito authentication or similar measures for long-term use.


Closing

By placing CloudFront in front, we were able to resolve the certificate-related limitations of code-server.
Since it only requires adding three things to the previous console procedure and CloudFormation template — security group, code-server configuration, and EIP with CloudFront distribution — applying it to an existing environment should not be difficult.

I hope this article is helpful to someone. Thank you for reading to the end!

References

https://dev.classmethod.jp/articles/202608-ec2-code-server-claude-code-bedrock/

https://aws.amazon.com/jp/blogs/news/limit-access-to-your-origins-using-the-aws-managed-prefix-list-for-amazon-cloudfront/


Claudeならクラスメソッドにお任せください

クラスメソッドは、Anthropic社とリセラー契約を締結しています。各種製品ガイドから、業種別の活用法、フェーズごとのお悩み解決などサービス支援ページにまとめております。まずはご覧いただき、お気軽にご相談ください。

サービス詳細を見る

Share this article

AWSのお困り事はクラスメソッドへ

Related articles