Skip to content

Coder Workspace

Coder is a self-hosted development environment platform that provisions dedicated workspaces for each user. The Ansible Epic Coder workspace runs as an AWS ECS Fargate container connected to a shared EFS volume, and is accessible from VS Code Desktop, VS Code Web, or any terminal via coder ssh.

Advantages over the manual VS Code tunnel

  • No login codes — your workspace starts immediately with your identity pre-configured
  • Persistent home directory across restarts (SSH keys, shell history, VS Code extensions, Claude Code auth)
  • Per-user isolation — your workspace is yours alone, no sharing or coordination required
  • Optional self-hosted GitHub Actions runner in every workspace
  • Selectable CPU and memory per workspace

Accessing Your Workspace

Coder Dashboard

Navigate to https://coder.sapphirehealth.org and log in with your Sapphire credentials. Your workspace appears on the dashboard. Click Start if it is stopped, then connect using one of the methods below.

VS Code Desktop

  1. Install the Coder VS Code extension.
  2. Open the Command Palette (Ctrl+Shift+P) and select Coder: Connect to workspace.
  3. Follow the prompts to authenticate and select your workspace.

VS Code Desktop connects through the Coder agent — no inbound firewall rules or open ports are required on your machine or the container.

VS Code Web (optional)

If Enable VS Code Web is set to true for your workspace, a VS Code Web button appears on the workspace dashboard. Click it to open a full VS Code experience in your browser without installing anything locally.

Note

VS Code Web is disabled by default to save Fargate resources. Enable it in your workspace settings if you prefer browser-based editing.

Terminal (SSH)

coder ssh <workspace-name>

Workspace Parameters

These can be set at workspace creation and changed later via Settings on the workspace dashboard.

Parameter Default Options Description
Enable GitHub Actions Runner false true / false Register and start a self-hosted GitHub Actions runner. Logs stream in real time in the workspace Scripts panel.
GitHub Runner URL https://github.com/Sapphire-Health/ansible-epic Any repo or org URL Target repo or org for the self-hosted runner.
Enable VS Code Web false true / false Start a browser-based VS Code Web server. Leave disabled for VS Code Desktop users.
Install Claude Code CLI false true / false Install @anthropic-ai/claude-code at workspace start. Run claude in any terminal to use it.
CPU 2048 (2 vCPU) 512 / 1024 / 2048 Fargate task CPU units.
Memory 4096 (4 GB) 2048 / 4096 / 8192 Fargate task memory in MB.

Fargate CPU/memory constraints

Not all CPU and memory combinations are valid. 2 vCPU (2048) requires at least 4096 MB memory. See the AWS documentation for all valid combinations.


Persistent Storage

Because Fargate containers are ephemeral, the workspace uses a shared EFS volume mounted at /home/ansible/source. Your personal directory is /home/ansible/source/<username>, and the following home directory paths are symlinked to EFS on every start so they survive container restarts:

Path What persists
~/.vscode-server/ VS Code Desktop server binary and extensions
~/.vscode/ VS Code Web extensions and state
~/.ssh/ SSH keys and config
~/.claude/ Claude Code settings and conversation history
~/.claude.json Claude Code authentication token
~/.bash_history Shell history

Shell history is flushed after every command (PROMPT_COMMAND="history -a"), so it is preserved even if the terminal closes uncleanly.


GitHub Authentication

Coder integrates with GitHub via OAuth. Connect your GitHub account once in the Coder dashboard under Account → External Authentication, and authentication is handled automatically for both git operations over HTTPS and gh CLI commands — no passwords, tokens, or SSH keys required for GitHub access.

The Coder agent configures GIT_ASKPASS transparently so git clone, git push, git pull, and similar commands authenticate without any prompts. GH_TOKEN is also set in every terminal for the gh CLI.

The token is refreshed dynamically each time a new terminal opens, so it stays current as long as your Coder session is active.

Token Expiry

OAuth tokens can expire during a long-running terminal session. If gh CLI commands start failing with authentication errors, refresh the token manually:

export GH_TOKEN=$(/tmp/coder-agent external-auth access-token primary-github)

Modifying the Template

The workspace is defined in coder/dev-container/dev-container.tf. All infrastructure values are declared as locals at the top of the file — edit those rather than searching for hardcoded strings throughout.

Hardcoded Locals

Local Current Value Description
aws_region us-west-2 AWS region
vpc_id vpc-0955c9f0b9dad2b3e VPC for the ECS task
subnet_id subnet-0ba94effeab197e2d Subnet (public, with IGW)
efs_id fs-04661a8d21c22cb2c Shared EFS file system
account_id 271851283454 AWS account ID
sg_id sg-0a684dcb6fe276813 Shared security group
efs_ap_id fsap-0ecbddc08d79d817f Shared EFS access point
ecs_cluster_arn arn:aws:ecs:...:cluster/CoderCluster ECS cluster
log_group_name /ecs/CoderLogGroup CloudWatch log group
github_runner_pat_secret_arn arn:aws:secretsmanager:...:github_runner_pat-H25W0G Secrets Manager ARN for the GitHub runner PAT

Common Changes

Adding a new workspace parameter:

data "coder_parameter" "my_param" {
  name         = "my_param"
  display_name = "My Parameter"
  description  = "Description shown in the dashboard."
  type         = "bool"
  default      = false
  mutable      = true
}

Adding a startup script:

resource "coder_script" "my_script" {
  agent_id     = coder_agent.main.id
  display_name = "My Script"
  run_on_start = true
  script       = <<-EOF
    #!/bin/sh
    set -e
    echo "Running my script..."
  EOF
}

Tip

Add start_blocks_login = false to any long-running script (one that does not exit) so it does not block the terminal with a "startup scripts still running" banner.

Adding a persistent symlink — edit the coder_script.persist resource and add a link_persist call:

link_persist /home/ansible/.myapp "$PERSIST/.myapp"

For a file rather than a directory, follow the pattern used for ~/.claude.json in the persist script (check for an existing real file, seed EFS if present, then create the symlink).

Exposing a port as a dashboard app:

resource "coder_app" "myapp" {
  agent_id     = coder_agent.main.id
  slug         = "myapp"
  display_name = "My App"
  url          = "http://localhost:3000"
  icon         = "/icon/code.svg"
  subdomain    = false
  share        = "owner"
}

Coder CLI

The Coder CLI is required to push template changes and to SSH into workspaces from a terminal.

Install

winget install Coder.Coder
curl -fsSL https://coder.sapphirehealth.org/install.sh | sh

Log In

coder login https://coder.sapphirehealth.org

This opens a browser window to complete authentication with your Sapphire credentials. The session token is saved locally and reused on subsequent commands.

Keep It Updated

The CLI version must match the server version. Check for updates with:

winget upgrade Coder.Coder
curl -fsSL https://coder.sapphirehealth.org/install.sh | sh

Deploying Template Changes

After editing coder/dev-container/dev-container.tf, push the new template version with:

coder templates push dev-container --directory coder/dev-container

Note

Pushing a new template version does not affect running workspaces. Each user must click Update in their workspace dashboard and restart to pick up the changes.

Commit and push any changes to this repository as well so the template stays in sync with source control:

git add coder/dev-container/dev-container.tf
git commit -m "describe your change"
git push

Workspace Lifecycle

Event What happens
Start ECS service desired_count set to 1; new task definition revision created with a fresh Coder agent token; startup scripts run in order
Stop ECS service desired_count set to 0; SIGTERM sent to the container; startup.sh cleanly deregisters the GitHub Actions runner if enabled
Update New template version applied; workspace must be restarted to take effect

Startup Script Order

Scripts run in the order defined in main.tf:

  1. Persist User Data — creates per-user EFS directory and home directory symlinks
  2. VS Code Web (if enabled) — starts code serve-web in the background
  3. Git Config — sets user.name and user.email from your Entra SSO profile
  4. Claude Code CLI (if enabled) — installs Node.js and @anthropic-ai/claude-code
  5. GitHub Actions Runner (if enabled) — registers and starts the runner; logs stream to the Scripts panel