1. Architecture Overview
code-server wraps Visual Studio Code in a Node.js server process, exposing it as a browser-accessible web application with its own authentication, proxying, and CLI layer. It is built by Coder (coder.com), licensed MIT, and targets administrators who need to provide remote development environments accessible from any device with a modern browser.
1.1 High-Level Architecture
Browser (any device)
│
│ HTTPS / WSS
▼
┌─────────────────────────────────────────────────┐
│ code-server (Node.js 22 / Express 5) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ CLI/Entry │→│ Wrapper │→│ Child Process │ │
│ │ (entry.ts)│ │(parent) │ │ (actual server)│ │
│ └──────────┘ └──────────┘ └───────┬───────┘ │
│ │ │
│ ┌────────────────────────────────────┤ │
│ │ Express Router │ │
│ │ ┌─────┐ ┌─────┐ ┌──────┐ ┌────┐ │ │
│ │ │Login│ │Proxy│ │Health│ │VS │ │ │
│ │ │Route│ │Route│ │Route │ │Code│ │ │
│ │ └─────┘ └─────┘ └──────┘ └────┘ │ │
│ └────────────────────────────────────┘ │
│ │
│ VS Code Server (lib/vscode submodule + patches)│
└─────────────────────────────────────────────────┘
│
▼
Filesystem (extensions, settings, heartbeat)
1.2 Process Model — The Wrapper Pattern
code-server uses a parent/child process architecture implemented in src/node/wrapper.ts:
- Parent process (
ParentProcess): Spawns a child with the--childflag and an IPC channel. Forwards CLI arguments via a handshake message. Monitors the child for crashes and handles OS signals (SIGTERM, SIGINT, SIGHUP). - Child process (
ChildProcess): Receives arguments from the parent over IPC, then starts the actual HTTP server. CallspreventExit()to overrideprocess.exit()— this is critical because VS Code's internals callprocess.exit()in several places, which would crash the entire server without this guard. - ProcessError: Wraps exit codes so the wrapper can distinguish intentional shutdowns from crashes.
This isolation layer exists specifically because VS Code was designed as a desktop application and calls process.exit() in code paths that a long-running server must survive.
1.3 Request Flow
Incoming request
│
├─ Cookie parser middleware
├─ Common middleware (injects args, heart, settings, updater)
├─ TLS redirect (if --cert configured)
│
├─ /_static/* → Static file serving (no auth)
├─ Domain proxy check → Domain-based proxy (if --proxy-domain matches hostname)
├─ /proxy/:port/* → Path-based proxy with URL rewriting
├─ /absproxy/:port/* → Absolute path proxy (passthrough, no rewriting)
├─ /healthz → Health check (HTTP GET or WebSocket)
├─ /login → Login page (GET) / password submission (POST)
├─ /logout → Session clearing
├─ /update/check → Version update check
├─ /vscode/* → VS Code web client (legacy prefix)
├─ /* → VS Code web client (primary)
└─ 404 → Catch-all error handler
1.4 Component Inventory
| Component | Path | Role |
|---|---|---|
| Entry point | src/node/entry.ts |
CLI parsing, dispatch to wrapper or VS Code CLI |
| CLI parser | src/node/cli.ts |
Argument parsing, config file loading, defaults |
| App factory | src/node/app.ts |
Express + HTTP(S) server creation |
| Route registry | src/node/routes/index.ts |
Middleware chain and route mounting |
| VS Code route | src/node/routes/vscode.ts |
Loads VS Code server module, delegates |
| Login route | src/node/routes/login.ts |
Login page rendering, password validation, rate limiting |
| Logout route | src/node/routes/logout.ts |
Cookie clearing |
| Health route | src/node/routes/health.ts |
Health check (HTTP + WebSocket) |
| Update route | src/node/routes/update.ts |
Version check endpoint |
| Domain proxy | src/node/routes/domainProxy.ts |
Hostname-based port proxying |
| Path proxy | src/node/routes/pathProxy.ts |
URL path-based port proxying |
| Error handlers | src/node/routes/errors.ts |
HTTP + WebSocket error handling |
| HTTP utilities | src/node/http.ts |
Auth, origin check, redirect, cookies, templates |
| Proxy server | src/node/proxy.ts |
http-proxy instance with redirect rewriting |
| Heart | src/node/heart.ts |
Heartbeat file writing, activity tracking |
| Settings | src/node/settings.ts |
JSON file-based settings read/write |
| Socket proxy | src/node/socket.ts |
TLS socket proxying for child processes |
| Update provider | src/node/update.ts |
GitHub release checking, version comparison |
| Utilities | src/node/util.ts |
Paths, password hashing (argon2/SHA256), cert generation |
| Constants | src/node/constants.ts |
Version strings, XDG paths, proxy env |
| Wrapper | src/node/wrapper.ts |
Parent/child process management |
| VS Code socket | src/node/vscodeSocket.ts |
Editor session manager IPC |
| WS router | src/node/wsRouter.ts |
WebSocket routing on top of Express |
| i18n | src/node/i18n/index.ts |
i18next initialization, custom string loading |
| Emitter | src/common/emitter.ts |
Event emitter with async support |
| HTTP types | src/common/http.ts |
HttpCode enum, HttpError class, cookie session name |
| Common utilities | src/common/util.ts |
plural, generateUuid, normalize, logError |
| Service worker | src/browser/serviceWorker.ts |
Minimal PWA service worker |
1.5 Technology Stack
| Layer | Technology | Version |
|---|---|---|
| Runtime | Node.js | 22 |
| Language | TypeScript | 5.6 |
| HTTP framework | Express | 5.0.1 |
| WebSocket | ws | 8.14 |
| Password hashing | argon2 | 0.44 |
| Proxy | http-proxy | 1.18 |
| TLS | httpolyglot | 0.1.2 |
| Config parsing | js-yaml | 4.1 |
| i18n | i18next | 25.8 |
| Rate limiting | limiter | 2.1 |
| Proxy agent | proxy-agent | 6.3 |
| Version comparison | semver | 7.5 |
| Log rotation | rotating-file-stream | 3.1 |
| Path resolution | env-paths 2.2, xdg-basedir 4.0 | — |
| Process init (Docker) | dumb-init | — |
| VS Code | Submodule at lib/vscode |
Patched via quilt |
1.6 VS Code Integration
VS Code source lives as a git submodule at lib/vscode. code-server applies 23+ quilt patches on top (see Section 8.5) to adapt the desktop editor for web serving. The VS Code server module is loaded dynamically by the VS Code route handler (src/node/routes/vscode.ts), which delegates all editor-related requests to VS Code's own HTTP handling.
2. System Requirements
2.1 Hardware
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 core | 2+ cores |
| RAM | 1 GB | 2+ GB (scales with extensions and open files) |
| Disk | 1 GB (base install) | 5+ GB (extensions, workspaces, user data) |
Note
Memory consumption scales linearly with the number of open files, active extensions, and language server instances. A TypeScript project with several extensions can consume 2-4 GB.
2.2 Software
- Node.js 22 (required, not optional)
- Linux is the primary target (Debian, Ubuntu, Fedora, openSUSE have Dockerfiles)
- macOS and Windows are supported but Linux is recommended for production
- A modern browser (Chrome, Firefox, Edge, Safari) on the client side
2.3 Network
- A single TCP port (default 8080) for HTTP/HTTPS and WebSocket traffic
- Optionally a Unix socket instead of TCP
- Outbound access to
api.github.comfor update checks (can be disabled)
3. Configuration Reference
3.1 Configuration Precedence
Configuration values are resolved in this order (highest wins):
- CLI flags (e.g.,
--auth none) - Environment variables (e.g.,
PASSWORD=secret) - Config file (
config.yaml) - Built-in defaults
3.2 Config File
The default config file location is {config_dir}/config.yaml. On first launch, code-server generates a default config with a random password:
bind-addr: 127.0.0.1:8080
auth: password
password: <randomly-generated>
cert: false
Override the config file path with --config <path> or $CODE_SERVER_CONFIG.
3.3 Complete CLI Reference
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--auth |
password or none |
password |
Authentication type | |
--password |
string | (auto-generated) | Password for login. Set in config or $PASSWORD only, not command line. |
|
--hashed-password |
string | Argon2 hash of password. Takes precedence over --password. Set in config or $HASHED_PASSWORD only. |
||
--cert |
optional string | false |
Path to TLS certificate. If flag is present without a value, generates a self-signed cert. | |
--cert-host |
string | localhost |
Hostname for self-signed certificate generation | |
--cert-key |
string | Path to TLS private key. Required when --cert has a path value. |
||
--disable-telemetry |
boolean | false |
Disable telemetry | |
--disable-update-check |
boolean | false |
Disable periodic update checks against GitHub releases | |
--session-socket |
string | {data}/code-server-ipc.sock |
Path for the editor session IPC socket | |
--cookie-suffix |
string | Suffix appended to cookie names. Also $CODE_SERVER_COOKIE_SUFFIX. |
||
--disable-file-downloads |
boolean | false |
Block file downloads. Also $CS_DISABLE_FILE_DOWNLOADS. |
|
--disable-file-uploads |
boolean | false |
Block file uploads | |
--disable-workspace-trust |
boolean | false |
Disable the workspace trust prompt (session only) | |
--disable-getting-started-override |
boolean | false |
Disable the Getting Started page override. Also $CS_DISABLE_GETTING_STARTED_OVERRIDE. |
|
--disable-proxy |
boolean | false |
Disable the built-in proxy routes. Also $CS_DISABLE_PROXY. |
|
--help |
-h |
boolean | Print help and exit | |
--link-protection-trusted-domains |
string[] | Domains that skip link protection warnings | ||
--locale |
string | IETF language tag for UI localization | ||
--open |
boolean | false |
Open browser automatically (only works when bound to localhost) | |
--bind-addr |
string | 127.0.0.1:8080 |
Address and port to bind. $PORT and $CODE_SERVER_HOST override the port and host components respectively. |
|
--config |
string | {config}/config.yaml |
Path to YAML config file. $CODE_SERVER_CONFIG. |
|
--host |
string | Deprecated — use --bind-addr |
||
--port |
number | Deprecated — use --bind-addr |
||
--socket |
string | Listen on a Unix socket instead of TCP. Overrides --bind-addr. |
||
--socket-mode |
string | File permissions for the Unix socket | ||
--trusted-origins |
string[] | Origins to allow. Supports * (all) and *.domain.com (wildcard subdomain). |
||
--version |
-v |
boolean | Print version and exit | |
--user-data-dir |
string | {data} |
VS Code user data directory | |
--extensions-dir |
string | {data}/extensions |
Extensions installation directory | |
--builtin-extensions-dir |
string | Additional built-in extensions directory | ||
--list-extensions |
boolean | List installed extensions and exit | ||
--force |
boolean | Skip prompts during extension install | ||
--locate-extension |
string[] | Print the path of specified extensions | ||
--category |
string | Filter extensions by category | ||
--install-extension |
string[] | Install extensions. Format: publisher.name[@version] or path to .vsix |
||
--enable-proposed-api |
string[] | Enable proposed API for specified extensions | ||
--uninstall-extension |
string[] | Uninstall specified extensions | ||
--show-versions |
boolean | Show extension versions when listing | ||
--github-auth |
string | GitHub token for extension marketplace auth. Set in config or $GITHUB_TOKEN only. |
||
--proxy-domain |
string[] | Domain pattern for domain-based proxying. Format: prefix-{{port}}.domain.tld or {{port}}.domain.tld. |
||
--skip-auth-preflight |
boolean | false |
Allow OPTIONS requests without authentication (for CORS preflight) | |
--ignore-last-opened |
-e |
boolean | false |
Do not reopen the last opened workspace |
--new-window |
-n |
boolean | false |
Open a new window |
--reuse-window |
-r |
boolean | false |
Reuse the existing window |
--log |
enum | Log level: trace, debug, info, warn, error. Also $LOG_LEVEL. |
||
--verbose |
-vvv |
boolean | false |
Sets log level to trace |
--app-name |
-an |
string | code-server |
Application name. Replaces the {{app}} placeholder in templates. |
--welcome-text |
-w |
string | Deprecated — Login page welcome text | |
--abs-proxy-base-path |
string | Prefix prepended to absproxy requests | ||
--i18n |
string | Path to custom i18n JSON strings | ||
--idle-timeout-seconds |
number | Auto-shutdown after idle period. Must be >60. Also $CODE_SERVER_IDLE_TIMEOUT_SECONDS. |
||
--reconnection-grace-time |
string | 10800 (3 hours) |
Seconds to keep sessions alive for reconnection. Also $CODE_SERVER_RECONNECTION_GRACE_TIME. |
Important
The
--password,--hashed-password, and--github-authflags cannot be passed on the command line for security reasons. They must be set via config file or environment variables. The corresponding environment variables (PASSWORD,HASHED_PASSWORD,GITHUB_TOKEN) are cleared from the process environment immediately after being read.
3.4 Complete Environment Variable Reference
| Variable | Purpose |
|---|---|
PASSWORD |
Login password (cleared after read) |
HASHED_PASSWORD |
Argon2 hashed password (cleared after read; takes precedence over PASSWORD) |
GITHUB_TOKEN |
GitHub token for extension marketplace auth (cleared after read) |
PORT |
Override the port component of --bind-addr |
CODE_SERVER_HOST |
Override the host component of --bind-addr |
CODE_SERVER_CONFIG |
Path to config file (overrides default) |
CODE_SERVER_SESSION_SOCKET |
Path to session IPC socket |
CODE_SERVER_COOKIE_SUFFIX |
Suffix for cookie names |
CODE_SERVER_RECONNECTION_GRACE_TIME |
Reconnection grace period in seconds |
CODE_SERVER_IDLE_TIMEOUT_SECONDS |
Idle shutdown timeout in seconds |
LOG_LEVEL |
Log level (trace, debug, info, warn, error) |
CS_DISABLE_FILE_DOWNLOADS |
Set to true or 1 to disable file downloads |
CS_DISABLE_GETTING_STARTED_OVERRIDE |
Set to true or 1 to disable Getting Started override |
CS_DISABLE_PROXY |
Set to true or 1 to disable proxy routes |
EXTENSIONS_GALLERY |
JSON string for custom extensions marketplace configuration |
VSCODE_PROXY_URI |
URI pattern for VS Code's Ports tab |
HTTP_PROXY / http_proxy |
HTTP proxy for outbound connections |
HTTPS_PROXY / https_proxy |
HTTPS proxy for outbound connections |
NODE_ENV |
Set to development to enable i18n debug mode |
DOCKER_USER |
Docker: rename the default coder user |
ENTRYPOINTD |
Docker: directory of startup scripts to execute |
CODE_SERVER_PARENT_PID |
Internal: parent process PID for VS Code CLI |
VSCODE_IPC_HOOK_CLI |
Internal: VS Code CLI IPC socket path |
3.5 Data Directory Layout
| Platform | Data Directory | Config Directory | Runtime Directory |
|---|---|---|---|
| Linux | ~/.local/share/code-server |
~/.config/code-server |
$XDG_RUNTIME_DIR/code-server or /tmp |
| macOS | ~/.local/share/code-server |
~/.config/code-server |
$XDG_RUNTIME_DIR/code-server or /tmp |
| Windows | %APPDATA%/code-server |
%APPDATA%/code-server |
System temp directory |
Note
macOS uses XDG-style paths, not native macOS
~/Librarypaths. This is intentional.
Key files within the data directory:
| File/Directory | Purpose |
|---|---|
config.yaml |
Main configuration (in config dir, auto-generated on first run) |
coder.json |
Settings file (last query, update check cache) |
heartbeat |
Heartbeat timestamp file (written every 60 seconds) |
code-server-ipc.sock |
Session manager IPC socket |
extensions/ |
Installed VS Code extensions |
serve-web-key-half |
Half of the encryption key for VS Code web |
4. Deployment & Scaling
4.1 Standalone Installation
Download the latest release from GitHub Releases and run:
# Install script (Linux/macOS)
curl -fsSL https://code-server.dev/install.sh | sh
# Or manually extract and run
tar -xzf code-server-4.116.0-linux-amd64.tar.gz
./code-server-4.116.0-linux-amd64/bin/code-server
On first run, a config file is generated at ~/.config/code-server/config.yaml with a random password. The server listens on 127.0.0.1:8080 by default.
4.2 Docker Deployment
The official Docker image is codercom/code-server:4.116.0. Base images are available for Debian (default), Fedora, and openSUSE.
docker run -d \
--name code-server \
-p 8080:8080 \
-v "$HOME/.local:/home/coder/.local" \
-v "$HOME/.config:/home/coder/.config" \
-v "$PWD:/home/coder/project" \
-u "$(id -u):$(id -g)" \
-e "DOCKER_USER=$USER" \
codercom/code-server:4.116.0
Docker-specific behavior:
- Default user:
coderwith UID 1000 DOCKER_USER: Renames thecoderuser to match your host user (for cleanerpsoutput and shell prompts)ENTRYPOINTD: Directory of shell scripts executed during container startup (useful for installing system packages, pre-installing extensions, etc.)- Process supervisor: Uses
dumb-initas PID 1 to handle signal forwarding and zombie reaping. An alternativecatatonitis also available. - fixuid: Handles UID/GID remapping when the container user doesn't match the mounted volume ownership
Tip
For persistent data across container recreations, mount
~/.local/share/code-server(extensions, settings) and~/.config/code-server(config file). Mount your project directory separately.
Docker Compose example:
services:
code-server:
image: codercom/code-server:4.116.0
ports:
- "8080:8080"
volumes:
- code-server-data:/home/coder/.local
- code-server-config:/home/coder/.config
- ./project:/home/coder/project
environment:
- DOCKER_USER=developer
- PASSWORD=changeme
restart: unless-stopped
volumes:
code-server-data:
code-server-config:
4.3 Kubernetes via Helm
The official Helm chart is at version 3.35.0 (appVersion 4.116.0).
helm repo add code-server https://helm.coder.com/code-server
helm install code-server code-server/code-server
Helm chart defaults:
| Setting | Default |
|---|---|
| Replicas | 1 |
| Service type | ClusterIP |
| Service port | 8080 |
| PVC size | 10Gi (ReadWriteOnce) |
| Security context | UID 1000 / GID 1000 |
| Liveness probe | Enabled |
| Readiness probe | Enabled |
| Volume permissions init container | Enabled |
Key values.yaml options:
# Extra CLI arguments
extraArgs:
- --auth=none
- --disable-telemetry
# Extra environment variables
extraVars:
- name: EXTENSIONS_GALLERY
value: '{"serviceUrl": "https://marketplace.example.com/_apis/public/gallery"}'
# Password from existing Kubernetes secret
existingSecret:
name: code-server-secret
key: password
# Pre-install extensions via init container
extraInitContainers:
- name: install-extensions
image: codercom/code-server:4.116.0
command: ["sh", "-c", "code-server --install-extension ms-python.python"]
volumeMounts:
- name: data
mountPath: /home/coder
# Sidecar container (e.g., Docker-in-Docker)
extraContainers:
- name: docker-dind
image: docker:dind
securityContext:
privileged: true
# Ingress
ingress:
enabled: true
hosts:
- host: code.example.com
paths:
- /
# Additional volume mounts
extraVolumeMounts: []
extraSecretMounts: []
# Lifecycle hooks
lifecycle: {}
Warning
code-server is a single-user application. Each instance serves one user. Do not set replicas > 1 for a single user. For multi-user deployments, create one code-server instance per user (separate Helm releases or a platform like Coder).
4.4 Reverse Proxy Configuration
code-server requires WebSocket support from any reverse proxy. The following are minimal configurations for common proxies.
Nginx:
server {
listen 443 ssl;
server_name code.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_set_header Accept-Encoding gzip;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Traefik (Docker labels):
labels:
- "traefik.enable=true"
- "traefik.http.routers.code-server.rule=Host(`code.example.com`)"
- "traefik.http.routers.code-server.tls.certresolver=letsencrypt"
- "traefik.http.services.code-server.loadbalancer.server.port=8080"
Caddy:
code.example.com {
reverse_proxy 127.0.0.1:8080
}
Important
When running behind a reverse proxy, set
--bind-addr 0.0.0.0:8080(or the appropriate internal interface) and configure--trusted-originsto include your domain. If you terminate TLS at the proxy, do not use--certon code-server itself.
4.5 systemd Service
[Unit]
Description=code-server
After=network.target
[Service]
Type=simple
User=coder
Group=coder
Environment=PASSWORD=changeme
ExecStart=/usr/bin/code-server --bind-addr 0.0.0.0:8080
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
4.6 Unix Socket Deployment
For reverse proxy setups where TCP isn't needed:
code-server --socket /run/code-server/code-server.sock --socket-mode 660
The reverse proxy connects to the Unix socket instead of a TCP port. This avoids port conflicts and can provide marginally better performance.
5. Operations
5.1 Health Monitoring
HTTP health check:
curl http://localhost:8080/healthz
# Response: {"status": "alive", "lastHeartbeat": <unix_timestamp>}
WebSocket health check: Connect a WebSocket to /healthz and send any message. The server responds with the same health JSON.
The heartbeat system writes a timestamp to {data}/heartbeat every 60 seconds. The /healthz endpoint returns the last heartbeat time, allowing external monitors to detect stalled processes (a running server that has stopped processing requests).
5.2 Update Management
By default, code-server checks GitHub releases for new versions periodically. The /update/check endpoint returns:
{
"checked": 1735689600,
"latest": "4.116.0",
"current": "4.115.0",
"isLatest": false
}
Disable this with --disable-update-check or CS_DISABLE_UPDATE_CHECK=true.
5.3 Extension Management
Extensions are managed via the CLI:
# Install an extension
code-server --install-extension ms-python.python
# Install a specific version
code-server --install-extension ms-python.python@2024.1.0
# Install from a .vsix file
code-server --install-extension ./my-extension.vsix
# List installed extensions
code-server --list-extensions --show-versions
# Uninstall
code-server --uninstall-extension ms-python.python
# Skip confirmation prompts
code-server --force --install-extension ms-python.python
Custom extension marketplace: By default, code-server uses the Open VSX Registry. To use a custom marketplace:
export EXTENSIONS_GALLERY='{"serviceUrl": "https://marketplace.example.com/_apis/public/gallery", "itemUrl": "https://marketplace.example.com/items"}'
Caution
The Microsoft Visual Studio Marketplace license prohibits use by non-Microsoft products. code-server uses Open VSX by default for this reason. Some extensions (GitHub Copilot, Microsoft-published extensions) may not be available on Open VSX. See known issue #7494 (Copilot Chat) and #7758 (Homebrew missing vscode.git extension).
5.4 Idle Timeout
Configure automatic shutdown after a period of inactivity:
code-server --idle-timeout-seconds 3600 # Shut down after 1 hour idle
The minimum value is 60 seconds. The idle timer resets on any browser activity that triggers the heartbeat. This is useful for cloud deployments where compute cost should be minimized.
5.5 Multi-Instance / Session Management
When code-server is already running and you invoke it with a file or folder argument, it uses the EditorSessionManager (via the IPC socket at {data}/code-server-ipc.sock) to open the file in the existing instance rather than starting a new server.
# Opens in the already-running instance
code-server /path/to/file.txt
Relevant flags:
--new-window/-n: Force a new window--reuse-window/-r: Reuse existing window--ignore-last-opened/-e: Don't reopen the last workspace
5.6 Logging
Control log verbosity:
code-server --log debug # debug level
code-server --verbose # trace level (most verbose)
LOG_LEVEL=warn code-server # via environment
Levels: trace, debug, info, warn, error.
Log output goes to stdout/stderr. Use rotating-file-stream (a dependency) if log rotation is configured. In Docker, rely on Docker's logging drivers to manage log retention.
5.7 Localization
Specify a locale for UI strings:
code-server --locale ja
Custom strings can be provided via --i18n /path/to/strings.json. The i18n system uses i18next internally. Setting NODE_ENV=development enables i18n debug mode.
6. Performance
6.1 Resource Consumption
code-server's resource consumption is dominated by VS Code and its extensions, not by the code-server wrapper layer. Key factors:
- Memory: Base ~200-400 MB. Each open workspace with language servers adds 100-500 MB. TypeScript projects with IntelliSense are the heaviest.
- CPU: Mostly idle. Spikes during extension host operations (IntelliSense, linting, search).
- Disk I/O: Extension installations, file watching, and search operations.
- Network: WebSocket traffic between browser and server. Proportional to typing speed, terminal output, and extension activity.
6.2 Reconnection Grace Time
The --reconnection-grace-time flag (default: 10800 seconds = 3 hours) controls how long the server keeps a session alive after the browser disconnects. During this period, unsaved work and terminal sessions are preserved. Reduce this value in resource-constrained environments:
code-server --reconnection-grace-time 1800 # 30 minutes
6.3 Proxy Performance
The built-in proxy (/proxy/:port/* and /absproxy/:port/*) uses Node.js http-proxy. It rewrites redirect headers for path-based proxying. For high-throughput proxied services, consider exposing them directly rather than routing through code-server's proxy.
6.4 Tuning Tips
- Disable unused features:
--disable-telemetry,--disable-update-checkto reduce background network calls - Limit extensions: Each extension consumes memory and potentially CPU; install only what's needed
- File watching: Large projects with many files can exhaust inotify watchers on Linux. Increase
fs.inotify.max_user_watches:
echo "fs.inotify.max_user_watches=524288" | sudo tee /etc/sysctl.d/40-code-server.conf
sudo sysctl --system
7. Security
7.1 Authentication
code-server supports two authentication modes:
| Mode | Flag | Behavior |
|---|---|---|
password (default) |
--auth password |
Login page with password prompt |
none |
--auth none |
No authentication — anyone with network access can use the editor |
Caution
Setting
--auth nonegives full shell and file access to anyone who can reach the server. Never use this on a network-accessible instance without additional access control (VPN, SSH tunnel, reverse proxy with its own auth).
7.2 Password Storage
Passwords are stored and verified using one of three methods, determined by getPasswordMethod() based on the hash format:
| Method | Format | Security |
|---|---|---|
| Argon2 (recommended) | Hash starts with $argon |
Memory-hard, resistant to GPU/ASIC attacks |
| SHA256 (legacy) | 64-character hex string | Fast hash, not recommended for passwords |
| Plain text | Anything else | No hashing, comparison only |
Generate an argon2 hash:
# Using npx
npx argon2-cli -e "your-password"
# Set in config
hashed-password: "$argon2i$v=19$m=4096,t=3,p=1$..."
Warning
Known issue #7696: If an attacker obtains the hashed password value, they can potentially use it directly to authenticate without knowing the plaintext. Store your config file with restrictive permissions (
chmod 600 ~/.config/code-server/config.yaml).
7.3 Login Rate Limiting
The login endpoint is protected by a RateLimiter class (using the limiter library) with two limits:
- 2 attempts per minute (burst protection)
- 12 attempts per hour (sustained brute-force protection)
These limits are hard-coded and not configurable.
7.4 Origin Validation and CSRF Protection
- HTTP requests: The server validates the
Originheader against theHostheader. Mismatches are rejected unless the origin is in--trusted-origins. - WebSocket connections: Origin checking prevents cross-site WebSocket hijacking.
--trusted-origins: Accepts exact domains,*(allow all), or*.domain.com(wildcard subdomain matching).--skip-auth-preflight: AllowsOPTIONSrequests without authentication, required for CORS preflight in some proxy setups.
7.5 Session Management
- Sessions use HTTP cookies with
sameSite=lax - Cookie domain is derived from the highest-level proxy domain (if configured)
- Cookie path is scoped to the base path
--cookie-suffixallows disambiguating cookies when running multiple instances on the same domain
7.6 Sensitive Environment Variable Handling
The following environment variables contain secrets and are cleared from process.env immediately after being read by the CLI parser:
PASSWORDHASHED_PASSWORDGITHUB_TOKEN
This prevents accidental leakage via child processes, /proc/*/environ, or crash dumps.
7.7 TLS Configuration
Self-signed certificate (development/testing):
code-server --cert
# Generates a self-signed cert for --cert-host (default: localhost)
Bring your own certificate (production):
code-server --cert /path/to/cert.pem --cert-key /path/to/key.pem
When --cert is configured:
- The server uses
httpolyglotto serve both HTTP and HTTPS on the same port - HTTP requests are automatically redirected to HTTPS
- The TLS redirect middleware is injected into the route chain
For production, terminate TLS at a reverse proxy (nginx, Traefik, Caddy) rather than using code-server's built-in TLS.
7.8 Proxy Security
The built-in proxy routes (/proxy/:port/*, /absproxy/:port/*, and domain-based proxy) require authentication. This means that services running on the same host are only accessible through code-server to authenticated users.
Disable the proxy entirely with --disable-proxy if it's not needed.
7.9 File Transfer Controls
code-server --disable-file-downloads # Prevent downloading files
code-server --disable-file-uploads # Prevent uploading files
These flags are useful in environments where data exfiltration or introduction of unauthorized files is a concern.
7.10 Security Hardening Checklist
- Use
--auth passwordwith an argon2 hashed password - Restrict config file permissions:
chmod 600 ~/.config/code-server/config.yaml - Bind to localhost (
127.0.0.1) and use a reverse proxy with TLS for external access - Set
--trusted-originsto your specific domain(s), never*in production - Use
--disable-proxyif the proxy feature is not needed - Use
--disable-file-downloadsand--disable-file-uploadsif file transfer is not required - Run as a non-root user (the Docker image defaults to UID 1000)
- Keep code-server updated (or disable update checks and manage updates externally)
- Increase
fs.inotify.max_user_watchesfor Linux deployments to prevent DoS via file watching exhaustion
8. Developer Internals
8.1 Build System
The build process involves three main steps:
- Compile TypeScript:
src/is compiled to JavaScript - Build VS Code: The
lib/vscodesubmodule is built with patches applied - Package: The result is packaged into distributable tarballs or Docker images
Quilt patches in patches/ are applied to the VS Code submodule before building. These patches adapt the desktop VS Code for web serving (see Section 8.5).
8.2 Entry Point Dispatch Logic
src/node/entry.ts follows this decision tree:
entry.ts
├─ Is this a child process (--child flag)?
│ └─ Yes → Handshake with parent via IPC, run server directly
│
├─ Is --help or --version?
│ └─ Yes → Print and exit
│
├─ Is an extension flag present (--install-extension, etc.)?
│ └─ Yes → runCodeCli() — spawns VS Code CLI subprocess
│
├─ Is a file/folder argument with an existing instance?
│ └─ Yes → openInExistingInstance() via IPC socket
│
└─ Otherwise → wrapper.start(args) — spawn child process
8.3 Server Creation (app.ts)
createApp() in src/node/app.ts:
- Creates an Express router
- If
--certis configured: creates anhttpolyglotserver (serves HTTP + HTTPS on the same port) - Otherwise: creates a plain
http.createServer - Creates a separate
wsRouterfor WebSocket handling via thehandleUpgrademechanism - Creates an
EditorSessionManagerserver on a separate Unix socket for IPC between instances - Returns
{ router, wsRouter, server, dispose() }
8.4 Route Registration (routes/index.ts)
The route registration in src/node/routes/index.ts follows this exact order:
- Heartbeat system: Starts writing to
{data}/heartbeatevery 60 seconds - Cookie parser: Parses cookies on all requests
- Common middleware: Injects
args,heart,settings,updater,cookieSessionNameinto the request object - TLS redirect: If
--certis configured, redirects HTTP to HTTPS - Static files:
/_staticserves static assets (no auth required) - Domain proxy: Checked first via hostname matching against
--proxy-domainpatterns - Path proxy:
/proxy/:port/*(rewriting) and/absproxy/:port/*(passthrough) - Health check:
/healthz(HTTP GET and WebSocket) - Login/logout:
/loginand/logout(only mounted when--auth password) - Update check:
/update/check - VS Code: Mounted at both
/vscodeand/(dual prefix for legacy compatibility) - 404 catch-all: Returns error page
- Error handlers: Separate handlers for HTTP and WebSocket errors
8.5 VS Code Patches
The patches/ directory contains 23+ quilt patches applied to the VS Code submodule. Each patch adapts a specific VS Code behavior for the browser-served context:
| Patch | Purpose |
|---|---|
base-path |
Support for serving behind a sub-path |
cli-window-open |
Adapt window opening for browser |
clipboard |
Browser clipboard integration |
copilot |
Copilot compatibility adjustments |
disable-builtin-ext-update |
Prevent built-in extensions from self-updating |
display-language |
Display language / locale support |
external-file-actions |
Handle external file operations in browser |
getting-started |
Custom Getting Started page |
insecure-notification |
Suppress insecure-connection warnings |
integration |
Core integration with code-server's HTTP layer |
keepalive |
WebSocket keepalive for long connections |
local-storage |
Browser localStorage adaptations |
logout |
Add logout functionality |
marketplace |
Extension marketplace (Open VSX) integration |
proposed-api |
Enable proposed API support |
proxy-uri |
Proxy URI handling for ports tab |
service-worker |
PWA service worker |
signature-verification |
Extension signature verification handling |
sourcemaps |
Source map handling for debugging |
store-socket |
Store socket path for IPC |
telemetry |
Telemetry handling |
trusted-domains |
Link protection and trusted domains |
unique-db |
Unique database paths per instance |
update-check |
Update notification integration |
webview |
Webview resource handling for browser |
8.6 Key Internal Modules
src/node/http.ts — HTTP utilities:
authenticated(): Checks cookie session for valid authenticationensureAuthenticated(): Middleware that redirects to login if not authenticatedensureOrigin(): Validates Origin header against Host and trusted originsredirect(): Constructs redirect responses with proper base path handlingreplaceTemplates(): Replaces{{CS_*}},{{BASE}},{{APP_NAME}}placeholders in HTML
src/node/proxy.ts — Proxy server:
- Creates an
http-proxyinstance - Rewrites
Locationheaders in redirect responses to maintain path-based routing - Handles both HTTP and WebSocket proxying
src/node/heart.ts — Heartbeat:
Heartclass writes the current timestamp to{data}/heartbeatevery 60 secondsbeat()is called on activity eventsalive()returns the last heartbeat timestamp
src/node/settings.ts — Settings:
SettingsProvider<T>reads and writes JSON settings files- Used for
coder.json(update check cache, last query time)
src/node/update.ts — Update provider:
UpdateProviderchecks GitHub releases for newer versions- Caches the result to avoid excessive API calls
- Compares versions using the
semverlibrary
src/common/emitter.ts — Emitter:
Emitter<T>provides type-safe event emitting withasyncsupport- Used throughout for internal event communication
8.7 WebSocket Router
src/node/wsRouter.ts provides WebSocket routing on top of Express. Since Express does not natively handle WebSocket upgrades, this module intercepts upgrade events on the HTTP server and routes them through a parallel set of route handlers that mirror the Express routing pattern.
8.8 Testing
| Test Type | Location | Framework |
|---|---|---|
| Unit tests | test/unit/ |
Jest + ts-jest |
| Script tests | test/scripts/ |
bats (Bash Automated Testing System) |
| Integration tests | test/integration/ |
Jest |
| E2E tests | test/e2e/ |
Playwright |
- Coverage threshold: 60% line coverage minimum
- Test isolation: Tests have their own
package.json(test/package.json) to avoid type conflicts with the VS Code submodule's TypeScript types - E2E auth state:
globalSetuppreserves authentication state across Playwright tests
# Run unit tests
npm test
# Run e2e tests
npx playwright test
# Run script tests (requires bats)
bats test/scripts/
8.9 Project Structure
code-server/
├── src/
│ ├── node/ # Server-side TypeScript
│ │ ├── entry.ts # Main entry point
│ │ ├── cli.ts # CLI argument parser
│ │ ├── app.ts # Express + HTTP server factory
│ │ ├── wrapper.ts # Parent/child process management
│ │ ├── routes/ # Route handlers
│ │ │ ├── index.ts # Route registration
│ │ │ ├── vscode.ts # VS Code delegation
│ │ │ ├── login.ts # Authentication
│ │ │ ├── health.ts # Health check
│ │ │ ├── pathProxy.ts
│ │ │ ├── domainProxy.ts
│ │ │ └── errors.ts
│ │ ├── http.ts # HTTP utilities
│ │ ├── proxy.ts # Proxy server
│ │ ├── heart.ts # Heartbeat
│ │ ├── settings.ts # JSON settings
│ │ ├── update.ts # Update checker
│ │ ├── util.ts # Utilities
│ │ ├── constants.ts # Version, paths
│ │ ├── socket.ts # TLS socket proxy
│ │ ├── vscodeSocket.ts # Session IPC
│ │ ├── wsRouter.ts # WebSocket router
│ │ └── i18n/ # Localization
│ ├── common/ # Shared code (server + browser)
│ │ ├── emitter.ts
│ │ ├── http.ts
│ │ └── util.ts
│ └── browser/ # Browser-side code
│ └── serviceWorker.ts
├── lib/
│ └── vscode/ # VS Code submodule (git submodule)
├── patches/ # Quilt patches for VS Code
├── test/
│ ├── unit/
│ ├── integration/
│ ├── e2e/
│ ├── scripts/
│ └── package.json # Separate test dependencies
├── ci/ # CI/CD configuration
└── docs/ # Documentation
9. Integration Points
9.1 HTTP API Reference
All endpoints served by code-server:
| Method | Path | Auth Required | Response |
|---|---|---|---|
| GET | /healthz |
No | {"status": "alive", "lastHeartbeat": <timestamp>} |
| WS | /healthz |
No | Send any message, receive health JSON |
| GET | /login |
No | HTML login page |
| POST | /login |
No | Validates password, sets session cookie, redirects |
| GET | /logout |
No | Clears session cookie, redirects to login |
| GET | /update/check |
Yes | {"checked": <ts>, "latest": "x.y.z", "current": "x.y.z", "isLatest": bool} |
| GET | /manifest.json |
No | PWA manifest |
| POST | /mint-key |
No | VS Code web encryption key |
| GET | /robots.txt |
No | Robots exclusion |
| GET | /security.txt |
No | Security contact information |
| ALL | /proxy/:port/* |
Yes | Path-based proxy with URL rewriting |
| ALL | /absproxy/:port/* |
Yes | Absolute path proxy (no rewriting) |
| ALL | /* (domain proxy) |
Yes | Domain-based proxy via --proxy-domain matching |
| GET | /vscode/* |
Yes | VS Code web client (legacy prefix) |
| GET | /* |
Yes | VS Code web client (primary) |
| GET | /_static/* |
No | Static assets |
9.2 Proxy Modes
code-server provides three proxy modes for accessing services running on the same host:
Path-based proxy (/proxy/:port/*):
- Rewrites URLs in responses to maintain correct routing
- Example:
https://code.example.com/proxy/3000/proxies tolocalhost:3000 - Rewrites
Locationheaders in redirects
Absolute path proxy (/absproxy/:port/*):
- Passes requests through without URL rewriting
- Configured base path via
--abs-proxy-base-path - Useful when the proxied application handles its own path prefix
Domain-based proxy (--proxy-domain):
- Routes based on hostname matching
- Format:
prefix-{{port}}.domain.tldor{{port}}.domain.tld - Example:
--proxy-domain "{{port}}.proxy.example.com"routes3000.proxy.example.comtolocalhost:3000 - Requires wildcard DNS and TLS certificate
The VSCODE_PROXY_URI environment variable integrates with VS Code's Ports tab, allowing forwarded ports to appear in the VS Code UI.
9.3 Extension Marketplace
By default, code-server connects to the Open VSX Registry. Override with the EXTENSIONS_GALLERY environment variable:
export EXTENSIONS_GALLERY='{
"serviceUrl": "https://marketplace.example.com/_apis/public/gallery",
"itemUrl": "https://marketplace.example.com/items"
}'
9.4 GitHub Integration
The --github-auth / $GITHUB_TOKEN setting provides a token that VS Code extensions can use for GitHub API authentication (e.g., for GitHub Pull Requests extension). The token is cleared from the environment after being read.
9.5 PWA Support
code-server includes a minimal service worker (src/browser/serviceWorker.ts) and PWA manifest (/manifest.json), allowing it to be installed as a Progressive Web App on supported browsers. This provides an app-like experience with its own window and taskbar icon.
9.6 IPC and Session Management
The EditorSessionManager (in src/node/vscodeSocket.ts) listens on a Unix socket ({data}/code-server-ipc.sock) and enables:
- Opening files in an existing instance from the command line
- Window management (
--new-window,--reuse-window) - Session discovery
Appendix A: Troubleshooting
A.1 Cannot find module 'argon2'
Error: Cannot find module 'argon2' on startup.
Cause: The argon2 native module failed to compile or is missing for the current platform/architecture.
Solution: Reinstall dependencies with npm rebuild argon2 or use a SHA256 hashed password as a workaround. The Docker image includes pre-built argon2 binaries.
Prevention: Use the official release binaries or Docker image, which include pre-compiled native modules.
Reference: GitHub issue #7588 (closed/resolved).
A.2 Copilot Chat Not Working
Error: GitHub Copilot Chat extension fails to activate or respond.
Cause: Copilot extensions may require specific VS Code APIs or authentication flows that differ between desktop VS Code and code-server's web environment.
Solution: Ensure you are using the latest code-server version. Check that the Copilot patch is applied correctly. Some Copilot features may have limited support in the web context.
Reference: GitHub issue #7494.
A.3 Extension Not Found or Wrong Version Installed
Error: CLI installs an older extension version than expected.
Cause: The Open VSX Registry may not have the latest version of the extension, or the version resolution differs from the VS Code Marketplace.
Solution: Specify the exact version: code-server --install-extension publisher.name@x.y.z. Alternatively, download the .vsix file directly and install it: code-server --install-extension ./extension.vsix.
Reference: GitHub issue #7631.
A.4 Always Asks to Trust Workspace
Error: The workspace trust dialog appears every time.
Cause: Workspace trust state may not persist correctly in certain configurations.
Solution: Use --disable-workspace-trust to suppress the dialog entirely.
Reference: GitHub issue #7703.
A.5 Dynamic Webview Resources Fail
Error: Webview content (e.g., extension UI panels) fails to load in some browsers.
Cause: Browser security policies or proxy configurations interfere with dynamically loaded webview resources.
Solution: Ensure your reverse proxy passes all required headers and does not strip or modify content. Check browser console for CSP or network errors.
Reference: GitHub issue #3410.
A.6 Random Text Insertion
Error: Random characters appear in the editor.
Cause: Input method or keyboard event handling differences between browser and native VS Code.
Solution: Update to the latest version. Try a different browser. Disable browser extensions that intercept keyboard events.
Reference: GitHub issue #7707.
A.7 --app-name Does Not Set Page Title
Error: Setting --app-name changes the login page branding but not the browser tab title.
Cause: The page title is controlled by VS Code's internal logic, not the --app-name template replacement.
Reference: GitHub issue #7688.
A.8 Security Vulnerabilities (Snyk)
Error: Snyk or other vulnerability scanners report issues.
Cause: Transitive dependencies may have known vulnerabilities.
Solution: Update to the latest code-server release, which periodically updates dependencies. Review the specific CVEs reported and assess whether they are exploitable in your deployment context.
Reference: GitHub issue #7737.
Appendix B: What Does NOT Exist
This section explicitly documents what code-server does not have, to prevent assumptions:
- No CLI subcommands: code-server uses a flat flag-based CLI parser, not a subcommand structure
- No built-in API beyond the routes listed in Section 9.1: There is no REST API for managing users, workspaces, or settings programmatically
- No database: All state is file-based (config YAML, JSON settings, filesystem)
- No background job queue: Everything is synchronous request/response or WebSocket
- No plugin system: An earlier version (v1) had a plugin API; it was removed. Extensions are VS Code extensions, not code-server plugins.
- No multi-user support: Each instance serves exactly one user. Multi-user requires multiple instances (or a platform like Coder).
- No built-in OAuth/SSO: Authentication is password-only. Use a reverse proxy (oauth2-proxy, Authelia, etc.) for SSO.
- No native macOS paths: macOS uses XDG-style paths (
~/.local/share,~/.config), not~/Library.