Browser VSCode docker image used to manage docker host with addon

Status: CURRENT — SECURITY SENSITIVE
Last reviewed: 31 August 2026
Applies to: LinuxServer.io code-server, Docker Engine, Docker Compose

This 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-docker installs 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.sock into 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:

  1. Do not publish the code-server hostname publicly.
  2. Use Tailscale when accessing your home network remotely.
  3. Use Tailscale Split DNS to resolve your private domain to your internal reverse proxy only while connected to your tailnet.
  4. Keep Authentik in front of code-server as another authentication layer. MFA is strongly recommended.
  5. 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-stopped


Check 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-docker


The Docker socket then needs to be mounted:

/var/run/docker.sock:/var/run/docker.sock:ro


Important: The :ro on 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: true


The 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-stopped


You would then access it at:

http://192.168.1.10:8443


Again: 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 ps


If the Docker Mod and socket are working, you should see the containers running on the host.

Docker Compose should also be available:

docker compose version


From 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 isolation


It is much closer to:

attacker → code-server → Docker socket → host control


That’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

Once it’s locked down properly, having direct browser access to your Compose files and the Docker CLI is bloody convenient 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *