|
| 1 | +# dstack Dev Box |
| 2 | + |
| 3 | +dstack v0.6 no longer ships a dev OS image with SSH and debugging tools. Add this one container to your compose file to get them back: root SSH into the CVM, a full Debian toolset, the host filesystem, the host Docker daemon, and a shell in the host OS itself. |
| 4 | + |
| 5 | +> [!WARNING] |
| 6 | +> This gives anyone holding an authorized key full root on the CVM: every container, the decrypted disk, and every secret in memory. It breaks the confidentiality guarantee your users rely on. The dev box is part of the attested `app-compose`, so verifiers can see it's there. Use it for development and debugging only. |
| 7 | +
|
| 8 | +## Usage |
| 9 | + |
| 10 | +Add the `dev-box` service from [`docker-compose.yaml`](./docker-compose.yaml) next to your workload: |
| 11 | + |
| 12 | +```yaml |
| 13 | +services: |
| 14 | + dev-box: |
| 15 | + image: ghcr.io/dstack-tee/dstack-dev-box:latest |
| 16 | + restart: unless-stopped |
| 17 | + privileged: true |
| 18 | + pid: host |
| 19 | + network_mode: host |
| 20 | + uts: host |
| 21 | + environment: |
| 22 | + - SSH_AUTHORIZED_KEYS=${SSH_AUTHORIZED_KEYS} |
| 23 | + volumes: |
| 24 | + - /:/host:rslave |
| 25 | + - /var/run/docker.sock:/var/run/docker.sock |
| 26 | + - /dev/pts:/dev/pts |
| 27 | + - ssh-host-keys:/etc/ssh/host-keys |
| 28 | + - home:/root |
| 29 | + |
| 30 | + your-app: |
| 31 | + image: ... |
| 32 | + |
| 33 | +volumes: |
| 34 | + ssh-host-keys: |
| 35 | + home: |
| 36 | +``` |
| 37 | +
|
| 38 | +Deploy with your public key or keys in `SSH_AUTHORIZED_KEYS`, in `authorized_keys` format with one key per line. The container exits with an error if the variable is empty. |
| 39 | + |
| 40 | +```bash |
| 41 | +phala deploy -n my-app -c docker-compose.yaml \ |
| 42 | + --image dstack-0.6.1 \ |
| 43 | + -e SSH_AUTHORIZED_KEYS="$(cat ~/.ssh/id_ed25519.pub)" |
| 44 | +``` |
| 45 | + |
| 46 | +On a plain dstack deployment, set `SSH_AUTHORIZED_KEYS` as an encrypted environment variable. |
| 47 | + |
| 48 | +## Connect |
| 49 | + |
| 50 | +`sshd` listens on port `1022`. Reach it through the dstack-gateway by adding this to `~/.ssh/config`: |
| 51 | + |
| 52 | +``` |
| 53 | +Host my-app |
| 54 | + User root |
| 55 | + ProxyCommand openssl s_client -quiet -connect <app-id>-1022.<gateway-domain>:443 -servername <app-id>-1022.<gateway-domain> -verify_hostname <app-id>-1022.<gateway-domain> -verify_return_error |
| 56 | +``` |
| 57 | + |
| 58 | +Replace `<app-id>` with your app ID and `<gateway-domain>` with the gateway base domain of the node the CVM runs on, for example `dstack-pha-prod15.phala.network`. Change `443` if your gateway uses a different port. |
| 59 | + |
| 60 | +> [!NOTE] |
| 61 | +> On macOS the system `openssl` is LibreSSL, which can time out with this `ProxyCommand`. Install OpenSSL with `brew install openssl` and make sure it's first on your `PATH`. |
| 62 | + |
| 63 | +## What you get |
| 64 | + |
| 65 | +You land in the dev box: a Debian trixie container, the same base as the dstack 0.6 guest OS, that shares the host's PID, network, and UTS namespaces. |
| 66 | + |
| 67 | +| Need | How | |
| 68 | +|------|-----| |
| 69 | +| Host processes, network, hostname | Visible directly: `ps`, `htop`, `ip`, `ss`, `tcpdump`, `strace -p` | |
| 70 | +| Host files | `/host`, for example `/host/dstack/app-compose.json` | |
| 71 | +| Containers | `docker` and `docker compose` talk to the host daemon | |
| 72 | +| systemd, journald, anything else on the host | `host-shell` opens a shell in the host OS; `host-shell CMD` runs one command there, for example `host-shell journalctl -u docker` | |
| 73 | +| File transfer | `scp`, `sftp`, and `rsync`, for example `rsync my-app:/host/tmp/dump ./` | |
| 74 | +| Port forwarding | `ssh -N -L 8080:127.0.0.1:8080 my-app` reaches any port on the CVM | |
| 75 | + |
| 76 | +Also installed: `vim`, `tmux`, `git`, `curl`, `jq`, `dig`, `gdb`, `lsof`, and `python3`. Run `apt-get install` for anything else. Installed packages last until the container is recreated. `/root` is a volume, so dotfiles and history persist across restarts and updates, as does the SSH host key. |
| 77 | + |
| 78 | +## How it works |
| 79 | + |
| 80 | +The host rootfs is a read-only, dm-verity-protected image, so tools can't be installed on the host itself. The dev box brings its own userland and borrows the host's namespaces instead, the same design as Bottlerocket's admin container and `kubectl debug node`. |
| 81 | + |
| 82 | +| Setting | Why | |
| 83 | +|---------|-----| |
| 84 | +| `privileged: true` | Full device access and capabilities for `tcpdump`, `strace`, `gdb`, and the `setns()` calls in `host-shell`. | |
| 85 | +| `pid: host`, `network_mode: host`, `uts: host` | The box sees the host's processes, network stack, and hostname. | |
| 86 | +| `/:/host:rslave` | The host filesystem, including mounts made after the box starts. | |
| 87 | +| `/var/run/docker.sock` | `docker` drives the host daemon. | |
| 88 | +| `/dev/pts:/dev/pts` | PTYs come from the host's devpts instance, so a terminal still works after `host-shell` switches to the host mount namespace. | |
| 89 | + |
| 90 | +[`host-shell`](./host-shell) runs `nsenter --target 1 --all --root`, joining every namespace of host PID 1 and its root directory. Its shell is the host's own `/bin/bash`, with no dev box tools. |
| 91 | + |
| 92 | +## Build your own image |
| 93 | + |
| 94 | +The compose file uses `ghcr.io/dstack-tee/dstack-dev-box:latest`, built from this directory by [`build-dev-box.yml`](../.github/workflows/build-dev-box.yml). To add packages, edit the [`Dockerfile`](./Dockerfile), push the image to a registry, and change `image:`. For a quick test, [ttl.sh](https://ttl.sh) holds anonymous images for up to 24 hours: |
| 95 | + |
| 96 | +```bash |
| 97 | +docker buildx build --platform linux/amd64 -t ttl.sh/my-dev-box:24h --push . |
| 98 | +``` |
0 commit comments