Normalizes non-standard code-fence language tags across `docs/**` so a strict highlighter (Shiki, used by Fumadocs) won't fail the build on an unrecognized language, and unifies redundant synonym tags onto one canonical form per language. The current renderer (Speed-Highlight) detects the language from the code content, not the fence label, so this drift wasn't visible until now. ## Changes - `hcl` -> `tf` (199 fences, including indented ones nested in numbered/bulleted lists). Shiki ships `hcl` and `terraform` as two distinct grammars (not aliases); every `hcl`-tagged fence in `docs/**` is actually Terraform resource/data/provider syntax, so the more specific `terraform` grammar is correct for all of them. `tf` is Shiki's own alias for that grammar, and it's also what GitHub's own markdown renderer resolves to the same HCL/Terraform highlighting. - `pwsh`/`powershell` -> `ps1`. Both `ps` and `ps1` are registered PowerShell aliases in Shiki, but on GitHub's renderer only `.ps1` is a registered file extension (`.ps` isn't), so `ps1` renders identically to `powershell` there today while bare `ps` would silently lose highlighting. - `env` -> `dotenv` (a dedicated Shiki grammar for `KEY=VALUE` files) - `text`/`output`/`none`/`url` -> `txt`. Same built-in plain-text fallback either way, just shorter. - `Dockerfile` -> `dockerfile` (lowercase) - `bash`/`shell` -> `sh` (732 fences). Shiki and GitHub both alias all three to a single shell grammar; this was already the style guide's stated preference, just not enforced across the existing corpus until now. - `markdown` -> `md` (4 fences). Alias of the same grammar in both Shiki and GitHub. - `jsonc` -> `json` (1 fence). The block has no comments or trailing commas, so it doesn't need the comments-capable grammar. - `ts` -> `tsx` (2 fences, `docs/about/contributing/frontend.md`). Verified the actual content tokenizes identically under both grammars, and a sibling block in the same file already needs `tsx` for real JSX, so unifying to one tag is safe for this file. Documented a caveat: `tsx` mis-tokenizes the legacy angle-bracket type-assertion syntax (`<Type>value`), which is invalid in real `.tsx` files anyway, so use `value as Type` instead. - `yml` -> `yaml` (1 fence) - Updated `docs/.style/style-guide/formatting.md` to document all canonical tags `promql` (2 fences) and `caddyfile` (2 fences) are left as-is. Shiki doesn't bundle a grammar for either, so they need a custom grammar registration when the site adopts Shiki, rather than degrading to `txt`. Tracked as follow-up work under DOCS-118 and [DOCS-544](https://linear.app/codercom/issue/DOCS-544/vendor-a-local-promql-grammar-for-shiki-syntax-highlighting) (promql). Does not touch `offlinedocs/`. Linear: [DOCS-476](https://linear.app/codercom/issue/DOCS-476/normalize-docs-code-fence-languages-de-risk-shikifumadocs) <details> <summary>How the fence tags were verified</summary> Each tag was tested against a real `shiki@latest` highlighter instance (`codeToHtml`/`codeToTokens`) and cross-checked against GitHub's `@wooorm/starry-night` grammar sources (the renderer that actually displays these `.md` files today, in repo browsing and PR diffs), since that's what determines whether brevity is safe before Shiki adoption: ```text FAIL env -- Language `env` is not included in this bundle. FAIL Dockerfile -- Language `Dockerfile` is not included in this bundle. FAIL promql -- Language `promql` is not included in this bundle. FAIL caddyfile -- Language `caddyfile` is not included in this bundle. FAIL pwsh -- Language `pwsh` is not included in this bundle. FAIL output -- Language `output` is not included in this bundle. ``` `hcl` doesn't error in Shiki, since it's a real grammar, but that's exactly the trap: it was silently rendering every fence with the generic HCL grammar instead of the Terraform-specific one. Every `hcl`-tagged fence in `docs/**` was manually checked against `origin/main` and is genuinely Terraform content. For `ts`/`tsx`, tokenizing the actual doc content confirmed identical output under both grammars; a synthetic test with the legacy angle-bracket cast syntax confirmed `tsx` degrades on that specific construct, which the style guide now calls out. The first normalization pass only matched fence tags at column 0 (`^```tag$`), missing tags indented inside numbered/bulleted lists. A follow-up pass caught the remaining occurrences at any indentation level. </details> --- *This PR description and the underlying changes were prepared with Coder Agents assistance.*
8.7 KiB
Persistent Shared Workspaces with Service Accounts
Note
This guide requires a Premium license because service accounts are a Premium feature. For more details, contact your account team.
This guide walks through setting up a long-lived workspace that is owned by a service account and shared with a rotating set of users. Because no single person owns the workspace, it persists across team changes and every user authenticates as themselves.
This pattern is useful for any scenario where a workspace outlives the people who use it:
- On-call rotations — Engineers share a workspace pre-loaded with runbooks, dashboards, and monitoring tools. Access rotates with the shift schedule.
- Shared staging or QA — A team workspace hosts a persistent staging environment. Testers and reviewers are added and removed as sprints change.
- Pair programming — A service-account-owned workspace gives two or more developers a shared environment without either one owning (and accidentally deleting) it.
- Contractor onboarding — An external team gets scoped access to a workspace for the duration of an engagement, then access is revoked.
The steps below use an on-call SRE workspace as a running example, but the same commands apply to any of the scenarios above. Substitute the usernames, group names, and template to match your use case.
Prerequisites
- A running Coder deployment (v2.32+) with workspace sharing enabled. Sharing is on by default for OSS; Premium deployments may require admin configuration.
- The Coder CLI installed and authenticated.
- An account with the
OwnerorUser Adminrole. - OIDC authentication configured so shared users log in with their corporate SSO identity. Configure refresh tokens to prevent session timeouts during long work sessions.
- A wildcard access URL configured
(e.g.
*.coder.example.com) so that shared users can access workspace apps without a 404. - (Recommended) IdP Group Sync configured if your identity provider manages group membership for the teams that will share the workspace.
1. Create a service account
Create a dedicated service account that will own the shared workspace. Service accounts are non-human accounts intended for automation and shared ownership. Because no individual user owns the workspace, there are no personal credentials to expose and the shared environment is not affected when any user leaves the team or the organization.
# On-call example — substitute a name that fits your use case
coder users create \
--username oncall-sre \
--service-account
2. Generate an API token for the service account
Generate a long-lived API token so you can create and manage workspaces on behalf of the service account:
coder tokens create \
--user oncall-sre \
--name oncall-automation \
--lifetime 8760h
Store this token securely (e.g. in a secrets manager like Vault or AWS Secrets Manager).
Important
Never distribute this token to end users. The token is for workspace administration only. Shared users authenticate as themselves and reach the workspace through sharing.
3. Create the workspace
Authenticate as the service account and create the workspace:
export CODER_SESSION_TOKEN="<token-from-step-2>"
coder create oncall-sre/oncall-workspace \
--template your-oncall-template \
--use-parameter-defaults \
--yes
Tip
Design a dedicated template for the workspace with the tools your team needs pre-installed (e.g. monitoring dashboards for on-call, test runners for QA). Set
subdomain = trueon workspace apps so that shared users can access web-based tools without a 404. See Accessing workspace apps in shared workspaces.
4. Share the workspace
Use coder sharing share to grant access to users who need the workspace:
coder sharing share oncall-sre/oncall-workspace --user alice
This gives alice the default use role, which allows connection via SSH and
workspace apps, starting and stopping the workspace, and viewing logs and stats.
To grant admin permissions (which includes all use permissions as well as renaming, updating, and inviting
others to join with the use role):
coder sharing share oncall-sre/oncall-workspace --user alice:admin
To share with multiple users at once:
coder sharing share oncall-sre/oncall-workspace --user alice:admin,bob
To share with an entire Coder group:
coder sharing share oncall-sre/oncall-workspace --group sre-oncall
Note
Groups can be synced from your identity provider using IdP Sync. If your IdP already manages team membership, sharing with a group is the simplest approach.
5. Rotate access
When team membership changes, remove outgoing users and add incoming ones:
# Remove outgoing user
coder sharing remove oncall-sre/oncall-workspace --user alice
# Add incoming user
coder sharing share oncall-sre/oncall-workspace --user carol
Important
The workspace must be restarted for user removal to take effect.
Verify current sharing status at any time:
coder sharing status oncall-sre/oncall-workspace
6. Automate access changes (optional)
For use cases with frequent rotation (such as on-call shifts), you can integrate the share/remove commands into external tooling like PagerDuty, Opsgenie, or a cron job.
Rotation script
#!/bin/bash
# rotate-access.sh
# Usage: ./rotate-access.sh <outgoing-user> <incoming-user>
WORKSPACE="oncall-sre/oncall-workspace"
OUTGOING="$1"
INCOMING="$2"
if [ -n "$OUTGOING" ]; then
echo "Removing access for $OUTGOING..."
coder sharing remove "$WORKSPACE" --user "$OUTGOING"
fi
echo "Granting access to $INCOMING..."
coder sharing share "$WORKSPACE" --user "$INCOMING"
echo "Restarting workspace to apply changes..."
coder restart "$WORKSPACE" --yes
echo "Current sharing status:"
coder sharing status "$WORKSPACE"
Group-based rotation with IdP Sync
If your identity provider manages group membership (e.g. an sre-oncall group
in Okta or Azure AD), you can skip manual share/remove commands entirely:
-
Configure Group Sync to synchronize the group from your IdP to Coder.
-
Share the workspace with the group once:
coder sharing share oncall-sre/oncall-workspace --group sre-oncall -
When your IdP rotates group membership, Coder group membership updates on next login. All current members have access; removed members lose access after a workspace restart.
Finding shared workspaces
Shared users can find workspaces shared with them:
# List all shared workspaces you can access, including your own
coder list --search shared:true
# List workspaces shared with you
coder list --search shared_with_user:me
# List workspaces shared with a specific user
coder list --search shared_with_user:alice
# List workspaces shared with a specific group
coder list --search shared_with_group:sre-oncall
Troubleshooting
Shared user sees 404 on workspace apps
Workspace apps using path-based routing block non-owners by default. Configure a
wildcard access URL and set
subdomain = true on the workspace app in your template.
Removed user still has access
Access removal requires a workspace restart. Run
coder restart <workspace> after removing a user or group.
Group sync not updating membership
Group membership changes in your IdP are not reflected until the user logs out
and back in. Group sync runs at login time, not on a polling schedule. Check the
Coder server logs with
CODER_LOG_FILTER=".*userauth.*|.*groups returned.*" for details. See
Troubleshooting group sync
for more information.
Next steps
- Shared Workspaces — full reference for workspace sharing features and UI
- IdP Sync — group, role, and organization sync configuration
- Configuring Okta — Okta-specific OIDC setup with custom claims and scopes
- Security Best Practices — deployment-wide security hardening
- Sessions and Tokens — API token management and scoping