Skip to content

code-server code-server

Description / nameInput element
Container Registry
Container Configuration Root Path
Timezone
User ID
Group ID
code-server Host Port
code-server /config Path

Build Status Last Commit OCI Pulls

VS Code in the browser — run a full development environment on your FreeBSD server and access it from anywhere.

Port 8080
Registry ghcr.io/daemonless/code-server
Daemonless daemonless/code-server
Source coder/code-server
Website coder.com/docs/code-server

Version Tags

Tag Description Best For
latest Upstream Binary. Built from official release. Most users — recommended.

Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions, including the security model for host vs. container privileges.

Deployment

services:
  code-server:
    image: "ghcr.io/daemonless/code-server:latest"
    container_name: code-server
    environment:
      - PUID=1000  # User ID for the application process
      - PGID=1000  # Group ID for the application process
      - TZ=UTC  # Timezone for the container
      - PASSWORD=<PASSWORD>  # Password for web UI (leave unset to disable auth)
      - DEFAULT_WORKSPACE=  # Default folder opened in the editor (default: /config/workspace)
      - DISABLE_MDO=  # Do not use FreeBSD's mac_do facility to allow executing commands as root from the terminal (optional)
    volumes:
      - "/path/to/containers/code-server:/config"
    ports:
      - "8080:8080"
    # always (not unless-stopped) so FreeBSD's podman rc.d auto-starts it at boot
    restart: always

Save as compose.yaml, then run podman-compose up -d.

podman run -d --name code-server \
  -p 8080:8080 \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=UTC \
  -e PASSWORD=<PASSWORD> \
  -e DEFAULT_WORKSPACE= \
  -e DISABLE_MDO= \
  -v /path/to/containers/code-server:/config \
  ghcr.io/daemonless/code-server:latest

Save as run.sh, then run sh run.sh.

- name: Deploy code-server
  containers.podman.podman_container:
    name: code-server
    image: "ghcr.io/daemonless/code-server:latest"
    state: started
    restart_policy: always
    env:
      PUID: "1000"
      PGID: "1000"
      TZ: "UTC"
      PASSWORD: "<PASSWORD>"
      DEFAULT_WORKSPACE: ""
      DISABLE_MDO: ""
    ports:
      - "8080:8080"
    volumes:
      - "/path/to/containers/code-server:/config"

Save as code-server-deploy.yaml, then run ansible-playbook code-server-deploy.yaml.

Warning

Exposing ports in AppJail means that your service can be reached from remote hosts. If that is not your intention, do not expose the ports and communicate with the service using the jail's IPv4 address or hostname assigned by the virtual network.

1
2
3
4
5
6
7
8
9
# .env

DIRECTOR_PROJECT=code-server
PUID=1000
PGID=1000
TZ=UTC
PASSWORD=<PASSWORD>
DEFAULT_WORKSPACE=
DISABLE_MDO=
# appjail-director.yml

options:
  - virtualnet: ':<random> default'
  - nat:
services:
  code-server:
    name: code_server
    options:
      - container: 'args:--pull'
      - expose: '8080:8080 proto:tcp'
    oci:
      user: root
      environment:
        - PUID: !ENV '${PUID}'
        - PGID: !ENV '${PGID}'
        - TZ: !ENV '${TZ}'
        - PASSWORD: !ENV '${PASSWORD}'
        - DEFAULT_WORKSPACE: !ENV '${DEFAULT_WORKSPACE}'
        - DISABLE_MDO: !ENV '${DISABLE_MDO}'
    volumes:
      - CODE_SERVER_CONFIG_PATH: /config
volumes:
  CODE_SERVER_CONFIG_PATH:
    device: '/path/to/containers/code-server'
1
2
3
4
5
6
7
# Makejail

ARG tag=latest

OPTION container=boot
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/code-server:${tag}

Save the files above, then run appjail-director up.

appjail oci run -Pd \
  -o overwrite=force \
  -o container="args:--pull" \
  -o virtualnet=":<random> default" \
  -o nat \
  -o expose="8080:8080 proto:tcp" \
  -e PUID=1000 \
  -e PGID=1000 \
  -e TZ=UTC \
  -e PASSWORD=<PASSWORD> \
  -e DEFAULT_WORKSPACE= \
  -e DISABLE_MDO= \
  -o fstab="/path/to/containers/code-server /config <pseudofs>" \
  ghcr.io/daemonless/code-server:latest code-server

Save the files above, then run sh run.sh.

Experimental

Bastille's OCI support is experimental. It requires buildah and shares the host network stack (inherit). Mount volumes with --volume HOST JAIL; without it, image-declared volumes are stored under ${bastille_volumesdir}/${jail}.

services:
  code-server:
    name: code-server
    image: "ghcr.io/daemonless/code-server:latest"
    network:
      - mode: host
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=UTC
      - PASSWORD=<PASSWORD>
      - DEFAULT_WORKSPACE=
      - DISABLE_MDO=
    volumes:
      - "/path/to/containers/code-server:/config"

Save as bastille-compose.yml, then run bastille up.

1
2
3
4
5
6
7
8
9
bastille create -O \
  --env PUID=1000 \
  --env PGID=1000 \
  --env TZ=UTC \
  --env PASSWORD=<PASSWORD> \
  --env DEFAULT_WORKSPACE= \
  --env DISABLE_MDO= \
  --volume /path/to/containers/code-server /config \
  code-server ghcr.io/daemonless/code-server:latest inherit

Access at: http://localhost:8080

Interactive Configuration

Parameters

Environment Variables

Variable Default Description
PUID 1000 User ID for the application process
PGID 1000 Group ID for the application process
TZ UTC Timezone for the container
PASSWORD <PASSWORD> Password for web UI (leave unset to disable auth)
DEFAULT_WORKSPACE `` Default folder opened in the editor (default: /config/workspace)
DISABLE_MDO `` Do not use FreeBSD's mac_do facility to allow executing commands as root from the terminal (optional)

Volumes

Path Description
/config Configuration directory

Ports

Port Protocol Description
8080 TCP Web UI

Work in Progress

This image is functional but may change significantly in a future release.

Common dev tools (gcc, clang, llvm, python, gmake, git, ssh) are baked into the image for now.

Running commands as root in Terminal

doas works out of the box -- it is setuid and configured with permit nopass keepenv bsd:

doas pkg install rust cargo
sudo is a shim that execs doas, so it works too. -E and -H are accepted and ignored (doas.conf already uses keepenv); other flags pass through.

su works as well: the image ships FreeBSD-pam, without which it fails with su: pam_start: System error.

Requires Podman 5.8.4 or newer

Older Podman dropped the setuid bit while unpacking images on FreeBSD, so doas, sudo and su all failed in the container. Fixed by container-libs#935. On older Podman, use mdo below.

As an alternative that does not depend on the setuid bit, FreeBSD's MAC framework provides the mdo command. The mac_do kernel module has to be loaded on the host which runs Podman before the container is started.
You can load the module at runtime by running

kldload mac_do
To load the module automatically during boot you can add it to rc.conf with
sysrc kld_list+=mac_do

If the mac_do module is loaded when the container starts, it will automatically install a rule that allows the bsd user to execute commands as root by running mdo <command>.

To disable the installation of the mac_do rule that allows the privilege elevation, you can set the DISABLE_MDO environment variable to true or yes:

1
2
3
4
services:
  code-server:
    environment:
      - DISABLE_MDO=true

Installing Packages

In the terminal:

doas pkg install rust cargo
Or, if the mac_do module is loaded on the host:
mdo pkg install...

You can also install packages from the host using podman exec:

1
2
3
4
5
# Podman
doas podman exec -it -u root code-server pkg install rust cargo

# AppJail
doas appjail cmd jexec code_server pkg install rust cargo

Packages are not persistent

If you recreate the container, you will need to reinstall any packages you have added.

Implementation Details

  • Architectures: amd64
  • User: bsd (UID/GID set via PUID/PGID). Defaults to 1000:1000.
  • Base: Built on ghcr.io/daemonless/base (FreeBSD 15.1).

Need help? Join our Discord community.