Skip to content

How to Deploy Remote Development Environment with VS Code Server and Amazon Q Developer CLI

13 minute read
Content level: Intermediate
1

Deploy a cloud-based VS Code Server that comes with the pre-installed Amazon Q Developer CLI, allowing full-stack development from any device, including an iPad.

Problem Statement

Modern developers face a critical challenge: their development environments are anchored to specific machines. A developer who starts work on their office computer cannot seamlessly continue on their iPad while traveling without complex setup and synchronization issues.

This creates three major problems: device mobility limitations, environment inconsistencies across team members leading to "works on my machine" issues, and the difficulty of accessing AI-powered tools like Amazon Q consistently across different devices. Developers are forced to choose between mobility and productivity.

Architecture Solution

Cloud-Based Development Environment

This solution creates a cloud-based development environment using AWS infrastructure that provides consistent, accessible development capabilities from any device with a web browser.

Running Q Dev CLI on VS Code Server on an iPad Running Q Dev CLI on VS Code Server on an iPad

This is how it works: When a developer accesses the environment, their browser connects to an EC2 instance running VS Code Server on port 8080. Nginx acts as a reverse proxy, forwarding requests to the VS Code Server while adding security headers. The Amazon Q CLI is pre-installed and accessible through the integrated terminal, providing AI assistance for development tasks without any local setup requirements.

Key Components:

  • EC2 Instance: Hosts VS Code Server and development tools
  • VS Code Server: Provides browser-based IDE functionality
  • Amazon Q CLI: Offers AI-powered development assistance
  • Nginx: Handles reverse proxy and security headers

Implementation Details

The solution uses Infrastructure as Code (CloudFormation) to deploy a complete development environment with automated configuration:

VS Code Server Configuration:

# Password-protected web IDE
bind-addr: 127.0.0.1:8080
auth: password
password: $GENERATED_PASSWORD
cert: false

Amazon Q CLI Installation:

# Installs all Q CLI binaries for full functionality
curl --proto "=https" --tlsv1.2 -sSf \
  "https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip" -o "q.zip"
unzip q.zip
cp q/bin/* ~/.local/bin/  # Copies q, qchat, qterm
chmod +x ~/.local/bin/*

Multi-Region Support: The template uses AWS Systems Manager Parameter Store to automatically select the correct Ubuntu AMI for any AWS region, ensuring deployment works across regions without modification.

Deployment Process

Save the CloudFormation template from the Appendix section and deploy using AWS CLI:

# Save the template from Appendix as a file
# Copy the CloudFormation template from the Appendix section below
# Save it as: code-server-q-dev.yaml

# Deploy the CloudFormation stack
aws cloudformation create-stack \
    --stack-name remote-dev-vscode-server \
    --template-body file://code-server-q-dev.yaml \
    --parameters \
        ParameterKey=ProjectName,ParameterValue=remote-dev \
        ParameterKey=InstanceType,ParameterValue=t3.small \
        ParameterKey=VolumeSize,ParameterValue=10 \
    --capabilities CAPABILITY_NAMED_IAM \
    --region us-east-1

# Wait for deployment completion
aws cloudformation wait stack-create-complete \
    --stack-name remote-dev-vscode-server \
    --region us-east-1

Get Access Information:

# Retrieve the access URL
aws cloudformation describe-stacks \
    --stack-name remote-dev-vscode-server \
    --region us-east-1 \
    --query 'Stacks[0].Outputs[?OutputKey==`VSCodeServerUrl`].OutputValue' \
    --output text

Testing and Validation

Step 1: Verify Deployment Status

Check that all CloudFormation resources are created successfully:

aws cloudformation describe-stacks \
    --stack-name remote-dev-vscode-server \
    --region us-east-1 \
    --query 'Stacks[0].StackStatus'

Deployment Output via AWS Console CloudFormation Deployment Output via AWS Console

Expected result: "CREATE_COMPLETE"

Step 2: Retrieve Access Credentials

Get the generated password from EC2 console logs:

  1. Navigate to EC2 Console → Instances
  2. Select your VS Code Server instance
  3. Actions → Monitor and troubleshoot → Get system log
  4. Search for "VS CODE SERVER PASSWORD"
  5. Copy the generated password

Step 3: Access the Development Environment

  1. Open web browser on any device (iPad, laptop, desktop)
  2. Navigate to the provided access URL
  3. Enter the retrieved password
  4. Verify VS Code Server loads successfully

VS Code Server Access VS Code Server login screen showing password authentication

Step 4: Test Amazon Q CLI Integration

Open the integrated terminal in VS Code Server and run:

# Verify Q CLI installation
q --version

# Login to Amazon Q using device flow
q login --use-device-flow

# Test chat functionality after login
q chat

Note: Amazon Q offers both Q Builder (free tier) with basic AI assistance and Q Developer Pro with advanced features. You can start with the free tier and upgrade to Pro License for enhanced capabilities like advanced code generation and enterprise features.

Expected results:

  • Version information displays correctly
  • Device flow login completes successfully
  • Chat interface launches without errors
  • AI assistance responds to development queries Amazon Q CLI Test Amazon Q CLI working in VS Code Server terminal

Security Considerations

This solution implements basic security measures: security groups restrict access to HTTP only, SSH is disabled, and data uses encrypted storage.

The main limitation is HTTP-only traffic, which transmits data in plaintext.

For production use, consider adding HTTPS certificates to meet enterprise security requirements.

Conclusion

This solution successfully addresses remote development challenges by providing universal device access, consistent development environment with AI assistance, and rapid deployment capabilities. The cost-effective operation at approximately $13-14 per month transforms any device into a powerful development workstation.

Appendix: Complete Infrastructure Code

CloudFormation Template (code-server-q-dev.yaml):

AWSTemplateFormatVersion: '2010-09-09'
Description: 'VS Code Server - Professional Minimal Template (No SSH) for Multi-Region Support'

Parameters:
  ProjectName:
    Type: String
    Default: 'vscode-server'
    Description: 'Name of the project (used for resource naming)'
  
  InstanceType:
    Type: String
    Default: 't3.small'
    AllowedValues: 
      - 't3.nano'
      - 't3.micro'
      - 't3.small'
      - 't3.medium'
      - 't3.large'
    Description: 'EC2 instance type (t3.small recommended for cost efficiency)'
  
  VolumeSize:
    Type: Number
    Default: 10
    MinValue: 8
    MaxValue: 50
    Description: 'EBS volume size in GB (10GB minimum for cost efficiency)'

Resources:
  # VPC - Minimal setup
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: '10.0.0.0/16'
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-vpc'

  # Internet Gateway
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-igw'

  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

  # Single Public Subnet
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: '10.0.1.0/24'
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-public-subnet'

  # Route Table
  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-public-rt'

  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable
      SubnetId: !Ref PublicSubnet

  # Security Group - HTTP only (no SSH)
  VSCodeSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub '${ProjectName}-sg'
      GroupDescription: 'Security group for VS Code Server (HTTP only)'
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: '0.0.0.0/0'
          Description: 'HTTP for VS Code Server'
      SecurityGroupEgress:
        - IpProtocol: -1
          CidrIp: '0.0.0.0/0'
          Description: 'All outbound traffic'
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-sg'

  # IAM Role for Systems Manager
  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${ProjectName}-ec2-role-${AWS::StackName}'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-ec2-role'

  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName: !Sub '${ProjectName}-ec2-profile-${AWS::StackName}'
      Roles:
        - !Ref EC2Role

  # EC2 Instance - No SSH key required
  VSCodeServerInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Sub '{{resolve:ssm:/aws/service/canonical/ubuntu/server/22.04/stable/current/amd64/hvm/ebs-gp2/ami-id}}'
      InstanceType: !Ref InstanceType
      SubnetId: !Ref PublicSubnet
      SecurityGroupIds:
        - !Ref VSCodeSecurityGroup
      IamInstanceProfile: !Ref EC2InstanceProfile
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
            VolumeType: gp3
            VolumeSize: !Ref VolumeSize
            DeleteOnTermination: true
            Encrypted: true
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash
          set -e
          
          # Set environment variables
          export HOME=/root
          export USER=root
          
          # Logging function
          log() {
              local message="[$(date '+%Y-%m-%d %H:%M:%S')] $1"
              echo "$message" | tee -a /var/log/vscode-setup.log
              echo "$message" > /dev/console 2>/dev/null || true
          }
          
          # Output password to console
          output_password() {
              local password="$1"
              echo "" > /dev/console 2>/dev/null || true
              echo "=========================================" > /dev/console 2>/dev/null || true
              echo "VS CODE SERVER PASSWORD" > /dev/console 2>/dev/null || true
              echo "=========================================" > /dev/console 2>/dev/null || true
              echo "Password: $password" > /dev/console 2>/dev/null || true
              echo "=========================================" > /dev/console 2>/dev/null || true
              echo "" > /dev/console 2>/dev/null || true
          }
          
          log "=== Starting VS Code Server setup (No SSH) ==="
          echo "VS Code Server setup starting..." > /dev/console 2>/dev/null || true
          
          # Wait for system initialization
          sleep 30
          
          # Function to wait for package locks
          wait_for_apt() {
              local max_attempts=20
              local attempt=1
              
              while [ $attempt -le $max_attempts ]; do
                  if fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; then
                      log "Package manager busy, waiting... ($attempt/$max_attempts)"
                      sleep 10
                      attempt=$((attempt + 1))
                  else
                      return 0
                  fi
              done
              
              killall apt apt-get dpkg 2>/dev/null || true
              rm -f /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock
              dpkg --configure -a
          }
          
          wait_for_apt
          
          # Update system
          log "Updating system..."
          apt update && apt upgrade -y
          
          # Install dependencies
          log "Installing dependencies..."
          apt install -y curl wget nginx nodejs npm unzip
          
          # Install code-server
          log "Installing code-server..."
          export HOME=/root
          curl -fsSL https://code-server.dev/install.sh | sh
          
          # Generate password
          log "Generating password..."
          PASSWORD=$(openssl rand -base64 32)
          echo "VS Code Server Password: $PASSWORD" > /home/ubuntu/vscode-password.txt
          chown ubuntu:ubuntu /home/ubuntu/vscode-password.txt
          chmod 600 /home/ubuntu/vscode-password.txt
          
          # Output password to console
          output_password "$PASSWORD"
          
          # Configure code-server
          log "Configuring code-server..."
          mkdir -p /home/ubuntu/.config/code-server
          cat > /home/ubuntu/.config/code-server/config.yaml << EOF
          bind-addr: 127.0.0.1:8080
          auth: password
          password: $PASSWORD
          cert: false
          disable-telemetry: true
          disable-update-check: false
          EOF
          
          chown -R ubuntu:ubuntu /home/ubuntu/.config
          
          # Start code-server
          log "Starting code-server..."
          systemctl enable --now code-server@ubuntu
          sleep 10
          
          # Verify code-server is running
          for i in {1..6}; do
              if systemctl is-active --quiet code-server@ubuntu; then
                  log "code-server service is running"
                  break
              else
                  log "Waiting for code-server to start... (attempt $i/6)"
                  sleep 10
              fi
          done
          
          # Configure Nginx
          log "Configuring Nginx..."
          cat > /etc/nginx/sites-available/vscode-server << 'EOF'
          server {
              listen 80;
              server_name _;
              
              # Security headers
              add_header X-Frame-Options DENY always;
              add_header X-Content-Type-Options nosniff always;
              add_header X-XSS-Protection "1; mode=block" always;
              server_tokens off;
              
              location / {
                  proxy_pass http://localhost:8080;
                  proxy_set_header Host $host;
                  proxy_set_header Upgrade $http_upgrade;
                  proxy_set_header Connection upgrade;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto $scheme;
                  proxy_http_version 1.1;
                  proxy_cache_bypass $http_upgrade;
              }
              
              # Health check
              location /health {
                  access_log off;
                  return 200 "healthy\n";
                  add_header Content-Type text/plain;
              }
          }
          EOF
          
          ln -sf /etc/nginx/sites-available/vscode-server /etc/nginx/sites-enabled/
          rm -f /etc/nginx/sites-enabled/default
          nginx -t && systemctl reload nginx
          
          # Create workspace
          log "Creating workspace..."
          mkdir -p /home/ubuntu/workspace
          cat > /home/ubuntu/workspace/README.md << 'WSEOF'
          # VS Code Server with Amazon Q CLI
          
          Welcome to your cloud development environment!
          
          ## Quick Start
          1. Open terminal: `q login --use-device-flow`
          2. Start coding with AI assistance: `q chat`
          3. Install extensions as needed
          
          ## Available Tools
          - VS Code Server (web IDE)
          - Amazon Q CLI (AI assistant)
          - AWS Toolkit
          - Python, Node.js/npm
          WSEOF
          
          chown -R ubuntu:ubuntu /home/ubuntu/workspace
          
          # Install Amazon Q
          log "Installing Amazon Q..."
          sudo -u ubuntu bash -c '
          cd /home/ubuntu
          curl --proto "=https" --tlsv1.2 -sSf "https://desktop-release.q.us-east-1.amazonaws.com/latest/q-x86_64-linux.zip" -o "q.zip" && \
          unzip q.zip && \
          mkdir -p ~/.local/bin && \
          cp q/bin/* ~/.local/bin/ && \
          chmod +x ~/.local/bin/* && \
          echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" >> ~/.bashrc && \
          rm -rf q.zip q/ && \
          ~/.local/bin/q --version
          '
          
          # Verify Q installation
          if sudo -u ubuntu /home/ubuntu/.local/bin/q --version >/dev/null 2>&1; then
              log "Amazon Q installed successfully"
              echo "Amazon Q installed successfully" > /dev/console 2>/dev/null || true
          else
              log "Amazon Q installation failed"
              echo "Amazon Q installation failed" > /dev/console 2>/dev/null || true
          fi
          
          # Install VS Code extensions
          log "Installing essential extensions..."
          sudo -u ubuntu code-server --install-extension ms-python.python || true
          sudo -u ubuntu code-server --install-extension esbenp.prettier-vscode || true
          sudo -u ubuntu code-server --install-extension amazonwebservices.aws-toolkit-vscode || true
          
          # Get instance info with retry logic
          log "Getting instance information..."
          INSTANCE_ID=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
          
          # Get public IP with retry and error handling
          PUBLIC_IP=""
          for i in {1..5}; do
              PUBLIC_IP=$(curl -s --connect-timeout 5 http://169.254.169.254/latest/meta-data/public-ipv4 2>/dev/null)
              if [ ! -z "$PUBLIC_IP" ] && [ "$PUBLIC_IP" != "null" ]; then
                  log "Public IP retrieved: $PUBLIC_IP"
                  break
              else
                  log "Attempt $i: Failed to get public IP, retrying in 10 seconds..."
                  sleep 10
              fi
          done
          
          # Fallback if public IP is still empty
          if [ -z "$PUBLIC_IP" ] || [ "$PUBLIC_IP" = "null" ]; then
              log "Warning: Could not retrieve public IP from metadata service"
              PUBLIC_IP="PENDING"
              echo "Warning: Public IP not available yet" > /dev/console 2>/dev/null || true
          fi
          
          # Create info file
          cat > /home/ubuntu/server-info.txt << INFOEOF
          VS Code Server Information
          =========================
          
          Instance ID: $INSTANCE_ID
          Public IP: $PUBLIC_IP
          Access URL: $([ "$PUBLIC_IP" = "PENDING" ] && echo "Check CloudFormation outputs" || echo "http://$PUBLIC_IP")
          Password: $PASSWORD
          
          Setup Date: $(date)
          Instance Type: ${InstanceType}
          Volume Size: ${VolumeSize}GB
          
          Quick Start:
          1. q login --use-device-flow
          2. q chat
          
          Note: SSH access is disabled by design for security.
          INFOEOF
          
          chown ubuntu:ubuntu /home/ubuntu/server-info.txt
          
          # Final console output
          echo "" > /dev/console 2>/dev/null || true
          echo "VS CODE SERVER SETUP COMPLETE!" > /dev/console 2>/dev/null || true
          echo "=========================================" > /dev/console 2>/dev/null || true
          if [ "$PUBLIC_IP" = "PENDING" ]; then
              echo "Access URL: Check CloudFormation outputs for IP" > /dev/console 2>/dev/null || true
              echo "Public IP: Will be available shortly" > /dev/console 2>/dev/null || true
          else
              echo "Access URL: http://$PUBLIC_IP" > /dev/console 2>/dev/null || true
          fi
          echo "Password: $PASSWORD" > /dev/console 2>/dev/null || true
          echo "Ready for iPad access!" > /dev/console 2>/dev/null || true
          echo "SSH disabled (web access only)" > /dev/console 2>/dev/null || true
          echo "=========================================" > /dev/console 2>/dev/null || true
          
          log "Setup completed successfully! No SSH access needed."
          if [ "$PUBLIC_IP" = "PENDING" ]; then
              log "Access URL: Check CloudFormation outputs for public IP"
              log "Public IP will be available shortly"
          else
              log "Access URL: http://$PUBLIC_IP"
          fi
          log "Password: $PASSWORD"
          
          # Test connectivity
          if curl -s http://localhost/health > /dev/null; then
              log "VS Code Server is responding correctly"
              echo "Setup successful! VS Code Server is ready." > /dev/console 2>/dev/null || true
          else
              log "Setup completed but health check failed"
              echo "Setup completed but health check failed" > /dev/console 2>/dev/null || true
          fi
      Tags:
        - Key: Name
          Value: !Sub '${ProjectName}-instance'
        - Key: Project
          Value: !Ref ProjectName

Outputs:
  InstanceId:
    Description: 'ID of the EC2 instance'
    Value: !Ref VSCodeServerInstance

  InstanceType:
    Description: 'EC2 instance type used'
    Value: !Ref InstanceType

  VolumeSize:
    Description: 'EBS volume size in GB'
    Value: !Ref VolumeSize

  InstancePublicIp:
    Description: 'Public IP address of the EC2 instance'
    Value: !GetAtt VSCodeServerInstance.PublicIp

  VSCodeServerUrl:
    Description: 'URL to access VS Code Server'
    Value: !Join ['', ['http://', !GetAtt VSCodeServerInstance.PublicIp]]

  GetPasswordInstructions:
    Description: 'How to get the VS Code Server password'
    Value: !Sub |
      GET PASSWORD (Multiple methods):
      
      Method 1 - EC2 Console:
      1. Go to EC2 Console → Instances → ${VSCodeServerInstance}
      2. Actions → Monitor and troubleshoot → Get system log
      3. Search for "VS CODE SERVER PASSWORD"
      
      Method 2 - Systems Manager (if needed):
      1. Go to Systems Manager → Session Manager
      2. Start session with ${VSCodeServerInstance}
      3. Run: cat /home/ubuntu/server-info.txt

  NextSteps:
    Description: 'Next steps after deployment'
    Value: !Join 
      - ''
      - - 'READY TO USE:\n'
        - '1. Wait 8-10 minutes for setup to complete\n'
        - '2. Get password from EC2 console output\n'
        - '3. Access: http://'
        - !GetAtt VSCodeServerInstance.PublicIp
        - '\n4. Enter password and start coding from iPad!\n'
        - '\nNo SSH needed - everything accessible via web browser!'

IMPORTANT: Please remember to DELETE YOUR STACK AFTER TESTING to avoid ongoing costs!

aws cloudformation delete-stack --stack-name remote-dev-vscode-server --region us-east-1
AWS
EXPERT
published 4 months ago476 views