zed devcontainer with ssh

The editor ‘zed’ does not support devcontainer. However, it does work with ssh.

Instead of direct support for devcontainers, ‘zed’ offers the Remote Development feature, which relies on a connection via ssh.

You can run an ssh server in a Docker container and get something close to a native devcontainer.

The idea comes from Supercharged Development with Zed.

This requires:

  • Dockerfile – Integration of ssh server into the devcontainer
  • docker-compose.yaml – Build and start configuration of the devcontainer
  • ~/.ssh/config – SSH client entry for easy access to the container
  • zed settings.json – appropriate remote config entry in the editor settings

Dockerfile

Example based on a small GO project. The image is essentially based on a Debian derivative.

#
# @see https://www.friedrichkurz.me/posts/2025-06-07-zed-devcontainer/
#

FROM docker.io/library/golang:latest

#
# zed didn't ask for a password - leave app user password empty
#
ARG APP_USER="app"
ARG APP_PASSWORD=""
ARG APP_DIR="/app"
ARG APP_UID="1000"
ARG SSH_PORT="2222"

#
# install ssh server and some essential development commands
#
RUN apt update \
    && apt install -y --no-install-recommends \
    iproute2 \
    net-tools \
    procps \
    ssh \
    sudo \
    && apt clean \
    && echo >/etc/sudoers.d/10-installer  "%sudo ALL=(ALL) NOPASSWD: ALL"

#
# basic sshd configuration
# - accept (empty) password authentication
# - listen on custom high port
# - accept some env variables
#
RUN cat <<EOT >/etc/ssh/sshd_config.d/app.conf
AcceptEnv LANG LC_* GIT_*
AllowUsers ${APP_USER}
AuthenticationMethods none
#AuthenticationMethods none,keyboard-interactive,password
KbdInteractiveAuthentication yes
LogLevel VERBOSE
PasswordAuthentication yes
PermitEmptyPasswords yes
Port ${SSH_PORT}
PrintMotd no
Subsystem sftp /usr/lib/openssh/sftp-server
UsePAM yes
EOT

#
# add development group and user
#
RUN groupadd -f -g ${APP_UID} ${APP_USER}
RUN useradd -u ${APP_UID} -g ${APP_UID} -G sudo -p "${APP_PASSWORD}" -m -s /bin/bash ${APP_USER}

#
# check sshd config
#
RUN /usr/sbin/sshd -t -e

#
# clone the relevant docker environment for ssh login
#
RUN env | grep -i go >> /home/${APP_USER}/.bashrc

#
# prepare app
#
USER ${APP_USER}
WORKDIR ${APP_DIR}

COPY --chown=${APP_UID}:${APP_UID} . ${APP_DIR}
RUN go mod vendor
RUN go build
RUN openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/C=DE/ST=Berlin/O=private/CN=devcontainer.local"
VOLUME [ "${APP_DIR}" ]

#
# start sshd
#
USER root
EXPOSE ${SSH_PORT}
CMD ["/usr/sbin/sshd", "-D", "-e"]

Docker Compose

The devcontainer can be started with docker compose up --build.

services:
  devcontainer:
    image: devcontainer:latest
    pull_policy: always
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - type: bind
        source: .
        target: /app
    ports:
      - name: ssh
        protocol: tcp
        app_protocol: ssh
        target: 2222
        published: 2222
      - name: http
        protocol: tcp
        app_protocol: http
        target: 80
      - name: https
        protocol: tcp
        app_protocol: https
        target: 443
      - name: http3
        protocol: udp
        app_protocol: http3
        target: 443
    cap_add:
      - CAP_NET_BIND_SERVICE
    networks:
      - localdev
    working_dir: /app
    tty: true

networks:
    localdev:
        driver: bridge
        driver_opts:
            # https://docs.docker.com/network/packet-filtering-firewalls/#setting-the-default-bind-address-for-containers
            "com.docker.network.bridge.host_binding_ipv4": "127.0.0.1"

ssh Config

Add a special host entry to ~/.ssh/config

#
# zed devcontainer
#
Host devcontainer
        Hostname localhost
        User app
        Port 2222
        StrictHostKeyChecking accept-new
        UserKnownHostsFile /dev/null

Settings in »zed«

In ‘zed’, the devcontainer can be easily opened using ssh devcontainer.

{
  "ssh_connections": [
    {
      "host": "devcontainer",
      "username": "app",
      "projects": [
        {
          "paths": [
            "/app"
          ]
        }
      ]
    }
  ],
}