Status: CURRENT — SECURITY SENSITIVE
Last reviewed: 31 August 2026
Applies to: LinuxServer.io code-server, Docker Engine, Docker ComposeThis setup deliberately gives a browser-accessible code-server instance control of the Docker daemon on the host. That is extremely powerful and should be treated as effectively giving the application administrator-level access to the Docker host.
What this does
This setup runs VS Code in the browser using LinuxServer.io’s code-server image, then adds the LinuxServer universal-docker mod so the Docker CLI and Docker Compose are available inside the container.
I also mount my host’s Docker Compose and Docker data folders into the code-server workspace. This lets me edit Compose files, configs and other Docker-related files from the browser, then use the built-in terminal to run normal Docker commands against the host.
The important bits are:
DOCKER_MODS=linuxserver/mods:universal-dockerinstalls the Docker CLI, Buildx and Compose plugin inside code-server.- The Docker socket is mounted into the container so those Docker commands control the host Docker daemon.
- Host folders containing Compose files and container data can be mounted into the workspace for easy editing.
⚠️ Security warning — read this before copying the Compose file
Mounting
/var/run/docker.sockinto a container is a major security decision. Docker’s own documentation warns that only trusted users should be able to control the Docker daemon because Docker can mount arbitrary host directories into containers. In practical terms, a process with unrestricted access to the Docker socket can usually gain root-level control of the host.That means if this code-server instance is compromised, you should assume the Docker host is compromised as well.
Do not expose this directly to the public internet unless you fully understand and accept that risk.
How I recommend accessing it
When I originally wrote this guide I used Traefik and Authentik in front of code-server. That’s still useful, but for an application with this much control I now recommend keeping it private to your LAN/Tailscale network wherever possible.
My preferred setup is:
- Do not publish the code-server hostname publicly.
- Use Tailscale when accessing your home network remotely.
- Use Tailscale Split DNS to resolve your private domain to your internal reverse proxy only while connected to your tailnet.
- Keep Authentik in front of code-server as another authentication layer. MFA is strongly recommended.
- Optionally keep code-server’s own hashed password enabled as a second independent authentication layer.
I have a separate guide for the private DNS side here: Tailscale Split DNS by Domain for Secure Home Server Access.
Tailscale’s current documentation calls this a restricted nameserver, or Split DNS. Queries for only your chosen private domain are sent to your own DNS server while other DNS continues normally.
Start with the LinuxServer.io code-server image
LinuxServer.io’s current base Compose example looks like this:
services:
code-server:
image: lscr.io/linuxserver/code-server:latest
container_name: code-server
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- PASSWORD=password # optional
- HASHED_PASSWORD= # optional
- SUDO_PASSWORD=password # optional
- SUDO_PASSWORD_HASH= # optional
- PROXY_DOMAIN=code-server.my.domain # optional
- DEFAULT_WORKSPACE=/config/workspace # optional
- PWA_APPNAME=code-server # optional
volumes:
- /path/to/code-server/config:/config
ports:
- 8443:8443
restart: unless-stoppedCheck the current LinuxServer.io code-server documentation before deploying, as image options can change over time.
Add Docker host control
LinuxServer provides the universal-docker Docker Mod, which adds the Docker CLI, Buildx and Docker Compose plugin to the container.
DOCKER_MODS=linuxserver/mods:universal-dockerThe Docker socket then needs to be mounted:
/var/run/docker.sock:/var/run/docker.sock:roImportant: The
:roon the bind mount does not make the Docker API read-only. It prevents the container from replacing or modifying the socket file itself, but commands sent through that socket can still create, stop, remove and otherwise control containers if the Docker API permits them.If you only need to view Docker information rather than manage the host, LinuxServer also publishes a Docker Socket Proxy that can restrict which API endpoints are available. That is safer, but it defeats the purpose of this particular setup if you want full Docker/Compose management.
My folder layout
On my Docker host I keep:
/home/richay/docker-compose/for Compose files and small hand-managed configuration files, and:
/home/richay/docker/for persistent container data.
Mounting those into the code-server workspace gives me quick browser access to the files I normally need to edit.
Recommended Compose — private reverse proxy + Authentik
This is the version closest to what I use. There is no published Docker port; Traefik reaches code-server over the external proxy network.
I recommend making the hostname available only through your LAN/private DNS or Tailscale Split DNS rather than creating a public DNS record for it.
services:
code-server:
image: lscr.io/linuxserver/code-server:latest
container_name: code-server
environment:
- PUID=1000
- PGID=1000
- TZ=Australia/Perth
- DEFAULT_WORKSPACE=/config/workspace
- PWA_APPNAME=code-server
- DOCKER_MODS=linuxserver/mods:universal-docker
# Optional but recommended as a second auth layer.
# Generate a strong hash rather than using PASSWORD in plain text.
# - HASHED_PASSWORD=$2a$...
volumes:
- /home/richay/docker/code:/config
- /home/richay/docker-compose:/config/workspace/docker-compose
- /home/richay/docker:/config/workspace/docker
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.code-secure.entrypoints=https"
- "traefik.http.routers.code-secure.rule=Host(`code-server.richay.au`)"
- "traefik.http.routers.code-secure.tls=true"
- "traefik.http.routers.code-secure.tls.certresolver=cloudflare"
- "traefik.http.routers.code-secure.service=code"
- "traefik.http.services.code.loadbalancer.server.port=8443"
- "traefik.http.routers.code-secure.middlewares=authentik@file"
networks:
- proxy
networks:
proxy:
external: trueThe port is deliberately not published with ports:. That stops code-server bypassing Traefik/Authentik through something like http://host-ip:8443.
With Split DNS, code-server.richay.au can resolve to the internal reverse proxy while you’re at home or connected to Tailscale, without exposing that hostname/service publicly.
If you don’t use a reverse proxy
You can still run code-server directly, but I would keep it on a trusted LAN/VPN rather than forwarding port 8443 from the internet.
Use a hashed password and bind the port to the Docker host’s private LAN address rather than every interface. Replace 192.168.1.10 with the actual LAN IP of your Docker host:
services:
code-server:
image: lscr.io/linuxserver/code-server:latest
container_name: code-server
environment:
- PUID=1000
- PGID=1000
- TZ=Australia/Perth
- HASHED_PASSWORD=$2a$...
- DEFAULT_WORKSPACE=/config/workspace
- PWA_APPNAME=code-server
- DOCKER_MODS=linuxserver/mods:universal-docker
volumes:
- /path/to/code-server/config:/config
- /path/to/docker-compose:/config/workspace/docker-compose
- /path/to/docker:/config/workspace/docker
- /var/run/docker.sock:/var/run/docker.sock:ro
ports:
- 192.168.1.10:8443:8443
restart: unless-stoppedYou would then access it at:
http://192.168.1.10:8443Again: do not port-forward 8443 from your router to this container. If you need it away from home, use Tailscale or another trusted VPN.
Test Docker access
Open a terminal inside code-server and run:
docker psIf the Docker Mod and socket are working, you should see the containers running on the host.
Docker Compose should also be available:
docker compose versionFrom there you can open your mounted Compose folder in VS Code, edit a stack and use the terminal as you normally would.
What happens if code-server is compromised?
This is worth spelling out because mounting the Docker socket can look harmless if you’ve never dealt with Docker’s security model.
An attacker who gains usable access to the Docker daemon could potentially create a container that mounts the host filesystem, access other containers and their volumes, read Docker secrets/configuration, start privileged workloads or otherwise take control of the machine.
So the security boundary is not:
attacker → code-server container → stopped by Docker container isolationIt is much closer to:
attacker → code-server → Docker socket → host controlThat’s why I now recommend keeping this service private behind Tailscale/LAN access even if you also use Authentik.
Optional: Docker Socket Proxy for read-only use
If your goal is only to view containers rather than start/stop/deploy them, don’t give code-server unrestricted access to the raw Docker socket.
LinuxServer’s Docker Socket Proxy can expose only selected Docker API endpoints. For example, a proxy can allow container listing while blocking API POST requests.
For full Docker Compose management this isn’t particularly useful because the permissions required to create containers and mount host paths are the same powerful permissions we’re trying to protect. But for monitoring/read-only workflows it’s a much safer design.
References
- LinuxServer.io code-server documentation
- LinuxServer universal-docker Mod
- Docker Engine security documentation
- Docker daemon socket protection
- Tailscale DNS / Split DNS documentation
- My Tailscale Split DNS guide
Once it’s locked down properly, having direct browser access to your Compose files and the Docker CLI is bloody convenient 🙂
