Based on sphinxcode/claude-code-server, adapted for TOTP 2FA (using Claude Opus 4.6).
Browser-based VS Code with Claude Code Chat Extension and CLI and TOTP two-factor authentication.
Deploy a full VS Code development environment in the cloud with Claude Code chat extension and CLI ready to go. Secured with TOTP 2FA (Google Authenticator, Authy, etc.) instead of a password. Access it from any browser, on any device.
- TOTP 2FA Authentication -- Secured with authenticator app, no passwords
- Claude Code CLI + Extension -- Use Claude in the terminal (
claude) or the VS Code chat sidebar -- both pre-installed, defaults to Opus - Browser-Based VS Code -- Full IDE experience accessible from any device
- Persistent Storage -- Extensions, settings, and projects survive redeploys (optional -- works without a volume too, you'll just re-enroll 2FA each deploy)
- GitHub CLI Pre-installed -- Run
gh auth loginto clone and push to private repos - Non-Root Security -- Runs as the
clauderuser with optional sudo access - Host-Agnostic -- Works with any Docker host (Coolify, fly.io, self-hosted, etc.)
docker build -t claude-code-server .
docker run -d \
-p 3000:3000 \
-v claude-data:/home/clauder \
-e ANTHROPIC_API_KEY=your-key-here \
--name claude-code \
claude-code-serverservices:
claude-code:
build: .
ports:
- "3000:3000"
volumes:
- claude-data:/home/clauder
environment:
- ANTHROPIC_API_KEY=your-key-here
restart: unless-stopped
volumes:
claude-data:This works with any Docker-based platform (Coolify, CapRover, Dokku, etc.):
- Point the service at your Git repo
- Set build method to Dockerfile
- Set the exposed port to 3000
- Add a volume mount for
/home/clauder(optional but recommended for persistence) - Add any environment variables you need (e.g.
ANTHROPIC_API_KEY) - Deploy -- the container handles everything else
- Deploy the container and open
http://localhost:3000(or your platform URL) - On first visit, you'll see a QR code -- scan it with your authenticator app (Google Authenticator, Authy, 1Password, etc.)
- Enter the 6-digit code from your authenticator to confirm enrollment
- You're in! VS Code opens in the browser
- Copy the TOTP secret from your container logs (see below) and save it as the
TOTP_SECRETenvironment variable in your platform -- this ensures your authenticator app keeps working across redeploys
On subsequent visits, just enter your 6-digit authenticator code to sign in. Sessions last 30 days by default, so you won't be prompted every time.
When the container first generates a TOTP secret, it prints it to the console logs:
════════════════════════════════════════════════════════════════
New TOTP secret generated. To persist across deploys without a
volume, set this environment variable:
TOTP_SECRET=JBSWY3DPEHPK3PXP...
════════════════════════════════════════════════════════════════
Copy that value and add it as an environment variable (TOTP_SECRET) in your hosting platform. On the next deploy, the container will use that secret automatically -- no new QR code, same authenticator code works.
If you use a persistent volume at /home/clauder, the secret is saved to disk and you don't need to set the env var. But without a volume, TOTP_SECRET is the way to avoid re-enrolling every deploy.
Resetting 2FA: To re-enroll with a new QR code, remove the
TOTP_SECRETenv var and either delete~/.config/claude-2fa/secret.jsonfrom the volume or redeploy without a volume.
Claude Code is available in two ways:
- Terminal -- Open the VS Code terminal and run
claude(interactive) orclaude-auto(auto-accept mode) - Chat Sidebar -- Click the Claude icon in the VS Code activity bar to use Claude directly in the editor's chat panel
The default model is Opus. You can change it with claude config set model sonnet or /model sonnet in a session.
Claude Code supports two authentication methods:
- API Key -- Set
ANTHROPIC_API_KEYas an environment variable (pay-per-use via Anthropic API) - OAuth Login -- Run
claudein the terminal and follow the prompts to log in with your Claude Pro or Max subscription (no API key needed)
If you don't set an API key, Claude will prompt you to authenticate via OAuth on first use.
GitHub CLI (gh) is pre-installed for authenticating with GitHub:
- Open the terminal in VS Code
- Run
gh auth login - Select GitHub.com → HTTPS → Login with a web browser
- Copy the one-time code, open the URL in another tab, and paste it
- Done -- git is now authenticated for private repos
This persists on the volume. Without a volume, you'll need to re-authenticate after each deploy.
| Variable | Required | Default | Description |
|---|---|---|---|
ANTHROPIC_API_KEY |
No | - | Anthropic API key for Claude CLI (or use OAuth login instead) |
TOTP_SECRET |
No | auto-generated | TOTP secret for 2FA (see below) |
SESSION_SECRET |
No | auto-generated | Secret for signing session cookies |
SESSION_MAX_AGE |
No | 30 days (ms) | Session cookie lifetime |
APP_NAME |
No | Claude Code Server |
Displayed on login page and banner |
CLAUDER_HOME |
No | /home/clauder |
Volume mount path |
RUN_AS_USER |
No | clauder |
Set to root if you need root access |
TOTP_SECRET: On first deploy, the container generates a TOTP secret and prints it to the console logs. Copy that secret and set it as theTOTP_SECRETenvironment variable -- this lets your authenticator app work across redeploys without needing a persistent volume. If set, the container auto-enrolls on startup (no QR code shown).
SESSION_SECRETis automatically generated and persisted to the volume on first run. Set it explicitly only if you need deterministic session secrets.
A volume is optional. Without one, all data is lost on every redeploy -- but if you set
TOTP_SECRETas an env var, your 2FA enrollment persists. I personally use this without a volume and just push any changes to my repo.
| Setting | Value |
|---|---|
| Mount Path | /home/clauder |
| Size | 5GB+ recommended |
Internet (port 3000) --> 2FA Auth Proxy (Express) --> code-server (localhost:8080, no auth)
Code-server runs with --auth none on its default port 127.0.0.1:8080 -- it is not reachable from outside the container. Only the auth proxy listens on the external port (3000). All traffic must pass through the proxy, which enforces TOTP authentication before forwarding requests.
TOTP (Time-based One-Time Password) generates a new 6-digit code every 30 seconds based on a shared secret between the server and your authenticator app. Both sides compute the same code from the secret + current time, so nothing sensitive is ever transmitted during login -- you just prove you have the secret by entering the right code.
- Enrollment: On first run, the server generates a random TOTP secret and displays it as a QR code. You scan it once with your authenticator app, and the secret is saved to the volume (
~/.config/claude-2fa/secret.json, file permissions0600). After enrollment, the QR code is never shown again. - Login: You enter the 6-digit code from your app. The server computes the expected code from the stored secret and compares. A window of +/-30 seconds is allowed to account for clock drift.
- No password at all -- authentication relies entirely on possession of the authenticator device.
| Measure | Detail |
|---|---|
| Internal binding | code-server binds to 127.0.0.1 only -- unreachable without the proxy |
| Rate limiting | 5 login attempts per 60 seconds to prevent brute force |
| Cookie flags | httpOnly (no JS access), sameSite: lax (CSRF protection) |
| Signed cookies | Session cookie is cryptographically signed with a secret key |
| WebSocket auth | WebSocket upgrade requests validate the session cookie before proxying; unauthenticated connections are destroyed |
| File permissions | TOTP secret file stored with 0600 permissions (owner-only read/write) |
Important: The auth proxy does not terminate TLS. In production, you should place it behind a reverse proxy that handles HTTPS (Coolify, Caddy, nginx, Cloudflare Tunnel, etc.). Without HTTPS, session cookies could be intercepted on the network.
- code-server -- VS Code in the browser
- Claude Code CLI -- AI coding assistant by Anthropic
- GitHub CLI -- GitHub authentication and private repo access
- otplib -- TOTP implementation
MIT