Ephemeral coding interview workstation. One command to install, a local browser dashboard to run it, one click to remove.
Two entry points, same installer — admins set up company-owned machines, candidates set up their own.
Admin (company-owned machine):
# macOS / Linux
curl -fsSL https://do.co/shellport-admin-mac | bash# Windows (PowerShell)
irm https://do.co/shellport-admin-win | iexCandidate (own machine):
# macOS / Linux
curl -fsSL https://do.co/shellport-macos | bash# Windows (PowerShell)
irm https://do.co/shellport-windows | iexPrerequisites: Docker (running) and Node.js. Your browser opens to http://localhost:3000 — everything runs locally, no cloud. The dashboard shows build progress, then "Start in" buttons for each detected editor (VS Code, Cursor, Windsurf, VSCodium…), the web editors vscode.dev and github.dev, and the container terminal.
The same one-liner is the MDM payload — no wrapper needed. ShellPort is per-user (it installs to ~/shellport, uses the logged-in user's Docker, and opens their browser), and the installer detects when an MDM runs it as root (Jamf) or SYSTEM (Intune) and automatically re-runs in the logged-in user's session, forwarding any SHELLPORT_* config.
- Jamf (macOS): paste
curl -fsSL https://do.co/shellport-admin-mac | bashinto a script and run it from a policy. Use a login or Self Service trigger so a user is present; if none is, the run exits cleanly so the next login retries. Set per-event config (questions, Slack token/channel, label) as policy parameters exported asSHELLPORT_*, or bake them into the release. - Intune (Windows): deploy
irm https://do.co/shellport-admin-win | iexas a platform script. Either set "Run this script using the logged-on credentials = Yes" (cleanest — runs as the user directly), or leave it as SYSTEM and the installer re-launches into the user's session via a one-shot scheduled task.
Prerequisites still apply per user: Docker (set to start at login) and Node.js must be installed for the logged-in user, or the installer stops with a clear message.
The IDE opens inside an isolated container with the latest Go, Python, Java, Node.js, C/C++, TypeScript, GitHub Copilot, Claude Code, GitHub CLI, doctl, Homebrew, neovim, jq, and yq. All candidate work must be saved in /workspaces.
Click "End Interview" in the dashboard, or run the script directly:
- macOS / Linux:
~/shellport/done.sh - Windows:
~\shellport\Done.ps1
This stops the server, kills IDE processes, tears down ShellPort's own container and volume, and deletes the project directory. It is scoped to ShellPort — never a host-wide Docker prune, and it leaves browsers and credentials untouched.
Two operator actions, both gated behind OS-level admin auth (macOS/Linux sudo/pkexec, Windows UAC) so a candidate session can't reach them:
- Recycle (DO station): scrub the host and load a fresh question for the next candidate without rebuilding the machine.
- End Event: tear down the workstation — power off a DO station, or return the uninstall command on a BYOD machine.
ShellPort detects its machine type. On a candidate's own (BYOD) machine it only ever removes its own footprint — never the aggressive host scrub reserved for dedicated DO stations.
Credential isolation is enforced via devcontainer.json: IDE settings block GitHub token injection, Git credential forwarding, SSH agent forwarding, and port forwarding; every spawned shell force-clears host tokens; the remote environment nullifies host variables and suppresses history. The container mounts only a named Docker volume — no host filesystem, Docker socket, or SSH agent socket is exposed.
Interviewer surfaces — Settings, telemetry, Recycle, End Event — are gated behind an admin unlock (the machine's OS-user password; no separate admin password) and never appear on a BYOD machine. Before each interview ShellPort confirms the host is clean and holds setup until any residue is cleared.
The troubleshooting panel (common fixes plus the manual fallback) is always available to a candidate on their own BYOD machine so they can recover unaided — destructive commands carry a warning and a confirm. On a managed DO station it is gated behind the same admin unlock, so a candidate session can't reach teardown/remove commands.
Off by default. Enable via .env or a config URL.
| Feature | .env variable | What it does |
|---|---|---|
| Timer | ENABLE_TIMER=true |
Live active/idle countdown. On expiry: NOTIFY, LOCK, or WIPE. Idle defined by INACTIVITY_TIMEOUT_MINUTES. |
| Telemetry | ENABLE_TELEMETRY=true |
Exports shell history, AI usage, and Git activity on cleanup. |
| Questions | QUESTIONS_URL=… |
Loads the assigned question during setup. Source is a Google Sheet of [title, docId] rows (QUESTION_ROW pins the row) or a Google Doc with one question per tab — all tabs are detected automatically and one is assigned at random (set QUESTION_TAB=t.<id> to pin a specific tab). The source is never sent to the candidate view — only the rendered question. |
| Question delivery | QUESTION_DELIVERY=auto |
auto renders inline, falls back to PDF; inline; pdf; none. |
| Notifications | SLACK_BOT_TOKEN=… |
Posts who/what/where to Slack when a question is assigned — candidate name, machine label, and the question. Preferred: a Slack App bot token (xoxb-…, scope chat:write) plus SLACK_CHANNEL — one token works across every blitz, just change the channel per event. Falls back to a single-channel incoming QUESTION_WEBHOOK if no token is set. Candidate name is set per-session in the dashboard. |
| Editors | ENABLED_EDITORS=… |
Comma-separated allow-list of local editors (e.g. VS Code,Cursor). Empty offers every detected editor. |
| Terminal | TERMINAL_ACCESS=false |
Hides the "Start in terminal" button. Default true. |
These settings, plus appearance (theme/accent/font), are also editable live in the dashboard under Admin → Settings.
.
├── app/
│ ├── server.js # Local orchestration server
│ ├── index.html # Dashboard frontend
│ └── package.json # Server dependencies (ws only)
├── .devcontainer/
│ ├── devcontainer.json # Container definition, tools, isolation
│ └── Dockerfile # System packages (always latest)
├── docker-compose.yml
├── install.sh / Install.ps1 # Candidate / universal installers
├── admin-install.sh / admin-install.ps1 # Managed DO station installers
├── done.sh / Done.ps1 # Cleanup
├── .env.defaults # Baked defaults copied into .env at install
├── .env.example # Documented reference for all options
├── .github/workflows/release.yml
├── admin/ # Optional MDM overlay (host sanitization)
│ ├── macos/reset.sh
│ └── windows/Reset.ps1
└── README.md
devcontainer.json works natively with GitHub Codespaces, DevPod, Coder, Daytona, and Gitpod — open the repo in any of them; no ShellPort install or cleanup scripts needed.
For company-owned machines cycled between candidates. The admin/ overlay adds host-level sanitization (browser/keychain/IDE wipe, history clear, post-wipe verification, container rebuild) and marks the machine as a managed DO station. It ships only in the admin release package.
sudo ./admin/macos/reset.sh # local, from inside the repo
sudo ./admin/macos/reset.sh /path/shellport # explicit path
# MDM push (Jamf/Intune) auto-discovers ~/shellportPush a tag; GitHub Actions builds the packages.
git tag -a v1.0.0 -m "Release" && git push origin v1.0.0| Package | Audience | Contains |
|---|---|---|
shellport-VERSION.tar.gz / .zip |
Everyone | Container + app + install + cleanup |
shellport-VERSION-admin.tar.gz / .zip |
IT only | Above + admin/ overlay |
install.sh / Install.ps1 |
Standalone | Candidate entry points |
admin-install.sh / admin-install.ps1 |
Standalone | Managed DO station entry points |
If the dashboard fails:
cd ~/shellport
docker compose up -d --build
docker compose exec interview-env bashClean up manually:
docker compose down -v --remove-orphans
rm -rf ~/shellportdocker compose down -v removes only ShellPort's own container and volume.
DigitalOcean IT — it@digitalocean.com