How to Self-Host code-server in 2026: WebSockets, Workspaces and Access Control
Self-host code-server with correct ports, persistent storage, HTTPS, secrets, backups and upgrade checks. Learn how to fix when the proxy blocks WebSockets.
A failed code-server deployment does not always crash. It may serve a login page while the proxy blocks WebSockets or file ownership prevents extension installs. Start with an end-to-end check instead: log in, open a mounted repository, create a file, run a terminal command, install an extension and reconnect the editor WebSocket.
That check matches the cataloged purpose of code-server: VS Code running in the browser on a remote machine. It also exposes missing dependencies, wrong proxy assumptions and ephemeral data earlier than an uptime probe can.
What code-server depends on
Draw three boundaries around code-server: ingress to port 8080, durable state and supporting requirements. The container is replaceable, but the other two need explicit owners. The local runtime requirement is a workspace mount containing only the projects the editor should reach. Test that boundary before publication and again after a container replacement.
The diagram is complete when a clean client can log in, open a mounted repository, create a file, run a terminal command, install an extension and reconnect the editor WebSocket. Capture timing and resource data for memory and CPU used by language servers, builds, extension hosts and terminals rather than the code-server web shell. If the transaction fails, the first boundary that does not behave as documented identifies whether to investigate routing, local capacity or a supporting service.
Turn the local command into an inspectable service
A production-shaped launch is intentionally boring: named state, explicit port and no secret inside the image.
docker run -d \
--name code-server \
--restart unless-stopped \
-p 127.0.0.1:8080:8080 \
-v code-server-data:/home/coder \
-e PASSWORD=replace-with-a-long-random-value \
codercom/code-server:latest \
--bind-addr 0.0.0.0:8080 --auth password .
The example is a baseline rather than a complete supporting stack. Confirm the local requirement before exposure: a workspace mount containing only the projects the editor should reach. Check the effective mounts and listener, then try to log in, open a mounted repository, create a file, run a terminal command, install an extension and reconnect the editor WebSocket. Pin the working image before the next restart.
Make the public origin unambiguous
Put the editor behind HTTPS and preserve WebSocket upgrades. Send the chosen hostname to container port 8080, forward the original host and HTTPS scheme, and avoid publishing a second direct origin.
Test code-server from a clean external client. Separate ingress failure from the known application boundary — the proxy blocks WebSockets or file ownership prevents extension installs. A certificate, DNS or 502 error belongs to routing; a request that reaches code-server and fails later belongs to application state, capacity or its supporting requirement. The custom-domain TLS guide covers the first group.
Back up the state code-server cannot recreate
A container image can be downloaded again; the configuration, extensions and explicitly mounted project directories cannot. Mount /home/coder before bootstrap, write harmless sample data and replace the container to prove that path is actually persistent. Inspect the effective mount instead of trusting a Compose filename, and check that the runtime user can write where code-server expects.
Choose retention and an off-host destination, then rehearse recovery without touching production. The drill passes only when settings, extensions and workspace files return with correct ownership and the terminal starts under the intended user. For database-backed state, pair storage snapshots with application-consistent exports as described in point-in-time recovery versus snapshots.
Lock down code-server after bootstrap
For code-server, the valuable surface is not necessarily the landing page. The main mistake is granting the container the Docker socket or whole host filesystem casually. Counter it deliberately: mount only intended workspaces, avoid the host Docker socket and place the editor behind both HTTPS and strong authentication.
Replace the sample PASSWORD immediately, store it outside the image and rotate it like an administrator credential if it is exposed. Use an unprivileged container user when the image supports it and mount no unrelated credentials. Apply rate or size limits at ingress where untrusted work can consume memory and CPU used by language servers, builds, extension hosts and terminals rather than the code-server web shell.
Diagnose a healthy-looking code-server
Observe the work code-server performs: memory and CPU used by language servers, builds, extension hosts and terminals rather than the code-server web shell. Set limits with headroom for that work and avoid a liveness probe that competes with it. The operator check should still attempt to log in, open a mounted repository, create a file, run a terminal command, install an extension and reconnect the editor WebSocket on a schedule.
For updates, remember that extension compatibility and base-image toolchains can change even when the code-server UI still starts. Deploy the candidate against a recovered copy and repeat the known test. If the proxy blocks WebSockets or file ownership prevents extension installs, use runtime logs and the actual network request to find which assumption changed.
Evidence to collect before code-server goes live
Create a small, disposable code-server fixture and keep it for every release. The fixture should exercise the real workflow: log in, open a mounted repository, create a file, run a terminal command, install an extension and reconnect the editor WebSocket. Record the image digest, external hostname, dependency address and the expected result so a later operator can repeat the test without interpreting this guide.
Run the fixture three times. First, use the fresh deployment. Second, replace the container without touching durable state. Third, restore the backup into an empty environment. The third run passes only when settings, extensions and workspace files return with correct ownership and the terminal starts under the intended user. During each run, capture latency and resource use around memory and CPU used by language servers, builds, extension hosts and terminals rather than the code-server web shell; this becomes the baseline for alerts rather than an arbitrary CPU percentage.
Finally, test the negative path deliberately: submit harmless input near the resource or format limit associated with this boundary: the proxy blocks WebSockets or file ownership prevents extension installs. Confirm that code-server fails visibly without corrupting state, restore the correct condition and repeat the successful transaction. A release record containing those four outcomes is stronger evidence than screenshots of a dashboard or a one-time curl response.
Move the repeatable infrastructure work to Dockup
Dockup can own the replaceable platform pieces: route traffic to port 8080, issue the domain and certificate, inject secrets, attach persistent storage and connect code-server to managed or privately attached services. It can do this on Dockup infrastructure or on a server you attach.
The code-server acceptance work remains explicit. After the one-click deployment, put the editor behind HTTPS and preserve WebSocket upgrades, confirm the local requirement — a workspace mount containing only the projects the editor should reach and run this scenario: log in, open a mounted repository, create a file, run a terminal command, install an extension and reconnect the editor WebSocket. That division is intentional: Dockup removes repetitive infrastructure setup without pretending that application roles, provider credentials or restore policy choose themselves.
Frequently asked questions
What does code-server need for a production deployment?
Route the code-server container on port 8080 through one HTTPS origin. The local runtime requirement is a workspace mount containing only the projects the editor should reach. Do not call code-server ready until you can log in, open a mounted repository, create a file, run a terminal command, install an extension and reconnect the editor WebSocket.
Which code-server data belongs in a backup?
Persist /home/coder and include the configuration, extensions and explicitly mounted project directories in the same recovery manifest. A clean code-server restore passes only when settings, extensions and workspace files return with correct ownership and the terminal starts under the intended user.
Does code-server require HTTPS behind a reverse proxy?
Use HTTPS for the public code-server origin and keep port 8080 on the internal route. Apply the code-server setting correctly: put the editor behind HTTPS and preserve WebSocket upgrades. For code-server, HTTPS protects credentials or user content in transit and keeps origin-sensitive client behavior consistent.
How should a code-server upgrade be tested?
Restore current code-server state into an isolated deployment, apply the candidate version and repeat its acceptance transaction. Pay particular attention because extension compatibility and base-image toolchains can change even when the code-server UI still starts. Keep the previous code-server image until its data-migration and rollback boundary are understood.
