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
- Install the Coder VS Code extension.
- Open the Command Palette (
Ctrl+Shift+P) and select Coder: Connect to workspace. - 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)
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:
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:
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
Log In
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:
Deploying Template Changes
After editing coder/dev-container/dev-container.tf, push the new template version with:
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:
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:
- Persist User Data — creates per-user EFS directory and home directory symlinks
- VS Code Web (if enabled) — starts
code serve-webin the background - Git Config — sets
user.nameanduser.emailfrom your Entra SSO profile - Claude Code CLI (if enabled) — installs Node.js and
@anthropic-ai/claude-code - GitHub Actions Runner (if enabled) — registers and starts the runner; logs stream to the Scripts panel