---
title: "Deploy Ubuntu AI Coding Agents Desktop (GUI)"
description: "Ubuntu desktop with Claude Code, Codex, Gemini, Copilot, OpenCode and aider"
category: "Other"
url: https://railway.com/deploy/ubuntu-ai-coding-agents-desktop-gui
---

# Deploy Ubuntu AI Coding Agents Desktop (GUI)

Ubuntu desktop with Claude Code, Codex, Gemini, Copilot, OpenCode and aider

**[Deploy Ubuntu AI Coding Agents Desktop (GUI) on Railway](https://railway.com/template/ubuntu-ai-coding-agents-desktop-gui)**

Machine-readable deploy manifest (JSON, validated by TemplateCI): https://railway.com/deploy/ubuntu-ai-coding-agents-desktop-gui/manifest.json

- **Creator:** darseen
- **Category:** Other
- **Total deploys:** 1

## Template content

### proxy https://github.com/caddyserver.png?size=460

- **Image:** caddy:2-alpine
- **Start command:** `sh -c 'printf "%s" "$CADDYFILE" > /etc/caddy/Caddyfile && exec caddy run --config /etc/caddy/Caddyfile --adapter caddyfile'`
- **Health check:** /proxy-healthz
- **Public domain:** Yes

### desktop https://devicons.railway.com/i/ubuntu.svg

- **Image:** kasmweb/ubuntu-noble-desktop:1.19.0-rolling-weekly
- **Start command:** `bash -c 'set -e
for k in ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY GH_TOKEN; do eval v=\$$k; [ -n "$v" ] || unset $k; done
if [ ${#VNC_PW} -lt 6 ]; then echo "FATAL: VNC_PW must be at least 6 characters (KasmVNC requirement). Set a longer password and redeploy." >&2; exit 1; fi
echo "kasm-user ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/kasm-user
chmod 0440 /etc/sudoers.d/kasm-user
chown 1000:1000 /home/kasm-user
if [ ! -f /home/kasm-user/.railway-owned ]; then chown -R 1000:1000 /home/kasm-user; touch /home/kasm-user/.railway-owned; chown 1000:1000 /home/kasm-user/.railway-owned; fi
sed -i "s|function wait_for_network_devices() {|function wait_for_network_devices() { return 0;|" /dockerstartup/vnc_startup.sh
grep -q "wait_for_network_devices() { return 0;" /dockerstartup/vnc_startup.sh || { echo FATAL: Kasm startup changed, wait_for_network_devices patch no longer applies >&2; exit 1; }
for b in claude aider; do ln -sf /home/kasm-user/.local/bin/$b /usr/local/bin/$b; done
for b in node npm npx corepack codex gemini copilot opencode; do ln -sf /home/kasm-user/.local/node/bin/$b /usr/local/bin/$b; done
cat > /dockerstartup/provision.sh <<"PROVEOF"
set -u
export HOME=/home/kasm-user
cd $HOME
for i in $(seq 1 60); do [ -f $HOME/.bashrc ] && break; sleep 1; done
log() { echo "[provision] $*"; }
NODE_DIR=$HOME/.local/node
if [ ! -x $NODE_DIR/bin/node ]; then
  log installing Node.js 24 LTS
  TARBALL=$(curl -fsSL https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt | grep -o "node-v[0-9.]*-linux-x64.tar.xz" | head -1)
  mkdir -p $NODE_DIR
  curl -fsSL "https://nodejs.org/dist/latest-v24.x/$TARBALL" | tar -xJ -C $NODE_DIR --strip-components=1 || log Node.js install failed
fi
export PATH=$HOME/.local/bin:$HOME/.local/node/bin:$PATH
export npm_config_cache=/tmp/npm-cache UV_CACHE_DIR=/tmp/uv-cache
if [ ! -x $HOME/.local/bin/claude ]; then
  log installing Claude Code
  curl -fsSL https://claude.ai/install.sh | bash || log Claude Code install failed
fi
npmi() { [ -x $HOME/.local/node/bin/$1 ] && return 0; log installing $2; npm install -g $2 >/dev/null 2>&1 || log $2 install failed; }
npmi codex @openai/codex
npmi gemini @google/gemini-cli
npmi copilot @github/copilot
npmi opencode opencode-ai
if [ ! -x $HOME/.local/bin/aider ]; then
  log installing aider
  curl -LsSf https://aider.chat/install.sh | sh >/dev/null 2>&1 || log aider install failed
fi
grep -q "local/node/bin" $HOME/.bashrc || echo "export PATH=\$HOME/.local/bin:\$HOME/.local/node/bin:\$PATH" >> $HOME/.bashrc
if ! ls $HOME/.vscode/extensions 2>/dev/null | grep -q anthropic.claude-code; then
  log installing the Claude Code VS Code extension
  code --no-sandbox --user-data-dir $HOME/.config/Code --install-extension anthropic.claude-code >/dev/null 2>&1 || log VS Code extension install failed
fi
rm -rf $HOME/.config/Code/CachedExtensionVSIXs $HOME/.cache/node-gyp
if [ ! -f $HOME/.config/Code/User/settings.json ]; then
  mkdir -p $HOME/.config/Code/User
  cat > $HOME/.config/Code/User/settings.json <<"JSONEOF"
{
  "workbench.startupEditor": "none",
  "telemetry.telemetryLevel": "off",
  "update.mode": "none"
}
JSONEOF
fi
mkdir -p $HOME/.local/share/icons $HOME/Desktop
geticon() { [ -s $HOME/.local/share/icons/$1.svg ] || curl -fsSL "$2" -o $HOME/.local/share/icons/$1.svg || rm -f $HOME/.local/share/icons/$1.svg; }
geticon claude https://cdn.simpleicons.org/claude/D97757
geticon codex https://devicons.railway.com/i/openai.svg
geticon gemini https://cdn.simpleicons.org/googlegemini/8E75B2
geticon copilot https://cdn.simpleicons.org/githubcopilot/FFFFFF
geticon opencode https://cdn.simpleicons.org/opencode/FFFFFF
launcher() { f=$HOME/Desktop/$1.desktop; [ -f $f ] && return 0; icon=utilities-terminal; [ -s $HOME/.local/share/icons/$4.svg ] && icon=$HOME/.local/share/icons/$4.svg
  printf "[Desktop Entry]\nType=Application\nName=%s\nExec=xfce4-terminal --title=\"%s\" --command=%s\nPath=$HOME\nIcon=%s\nTerminal=false\nCategories=Development;\n" "$2" "$2" "$3" "$icon" > $f; chmod +x $f; }
launcher claude-code "Claude Code" claude claude
launcher codex "Codex CLI" codex codex
launcher gemini-cli "Gemini CLI" gemini gemini
launcher copilot-cli "Copilot CLI" copilot copilot
launcher opencode "OpenCode" opencode opencode
launcher aider "aider" aider aider
for i in $(seq 1 120); do SESS=$(pgrep -u 1000 -x xfce4-session | head -1); [ -n "$SESS" ] && break; sleep 1; done
if [ -n "$SESS" ]; then
  export $(tr "\0" "\n" < /proc/$SESS/environ | grep ^DBUS_SESSION_BUS_ADDRESS=)
  for f in claude-code codex gemini-cli copilot-cli opencode aider; do gio set -t string $HOME/Desktop/$f.desktop metadata::xfce-exe-checksum "$(sha256sum $HOME/Desktop/$f.desktop | cut -d" " -f1)" || log could not mark $f trusted; done
fi
log done
PROVEOF
chmod 755 /dockerstartup/provision.sh
setpriv --reuid=1000 --regid=1000 --init-groups bash -c "bash /dockerstartup/provision.sh > /home/kasm-user/.provision.log 2>&1" &
exec setpriv --reuid=1000 --regid=1000 --init-groups /dockerstartup/kasm_default_profile.sh /dockerstartup/vnc_startup.sh /dockerstartup/kasm_startup.sh --tail-log'`

## Documentation

# Deploy and Host Ubuntu AI Coding Agents Desktop on Railway

![Ubuntu AI Coding Agents Desktop with one launcher per agent: Claude Code, Codex, Gemini CLI, Copilot CLI, OpenCode and aider](https://vaze.up.railway.app/api/hosting/railway-templates/ubuntu-ai-coding-agents-desktop-gui/ai-agents-desktop.png)

![OpenCode opened from its desktop icon](https://vaze.up.railway.app/api/hosting/railway-templates/ubuntu-ai-coding-agents-desktop-gui/ai-agents-desktop-opencode.png)

Ubuntu AI Coding Agents Desktop is a full Ubuntu 24.04 XFCE desktop that runs
in your browser with six coding agents already installed: Claude Code, OpenAI
Codex CLI, Gemini CLI, GitHub Copilot CLI, OpenCode and aider. VS Code, Chrome
and Node.js are there too. It is one cloud dev machine for trying every agent
side by side on the same repo, with a real editor and a real browser for the
OAuth logins, and no SSH key or VNC client.

## About Hosting Ubuntu AI Coding Agents Desktop

Deploying runs two services: the desktop itself, and a small Caddy proxy that
is the only public surface. The desktop image is Kasm's Ubuntu Noble workspace
(about 2.6 GB compressed), which already ships VS Code, Chrome, Chromium,
Firefox, git, tmux and Python. On first boot a background job installs Node.js
24 LTS, the six agents and the Claude Code VS Code extension into the home
directory, which lives on a Railway volume, so later boots skip the download.
The login page appears within a minute or two; the agents take about four
minutes more to finish installing, one desktop icon appearing per agent, and
`~/.provision.log` shows progress. The installed set takes about 2.3 GB of the
volume.

Cost is the thing to plan for. A graphical desktop idles near 1 GB of RAM with
a session attached, and more with Chrome and VS Code open. Enabling Railway's
app sleep is strongly recommended so the desktop suspends when you close the tab.

## Common Use Cases

- Comparing Claude Code, Codex, Gemini CLI, Copilot, OpenCode and aider on the same codebase without installing any of them locally
- A persistent agent workstation reachable from a tablet, a Chromebook or a locked-down work laptop
- Letting an agent run long tasks on a machine that stays up after you close your laptop

## Dependencies for Ubuntu AI Coding Agents Desktop Hosting

- A Railway volume mounted at `/home/kasm-user`, which holds the desktop's home directory, the installed agents and their logins. Without it, everything is lost on redeploy.
- An account or API key for each agent you want to use. Every agent has a browser or device-code login you can run from the desktop, or set `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GEMINI_API_KEY` and `GH_TOKEN` at deploy time. All four are optional.
- No GitHub repo backs this template. Both services boot from public images.

### Deployment Dependencies

- [kasmweb/ubuntu-noble-desktop](https://hub.docker.com/r/kasmweb/ubuntu-noble-desktop) - the upstream desktop image
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) - Anthropic's native installer
- [Codex CLI](https://github.com/openai/codex) - `@openai/codex` on npm
- [Gemini CLI](https://github.com/google-gemini/gemini-cli) - `@google/gemini-cli` on npm
- [GitHub Copilot CLI](https://github.com/github/copilot-cli) - `@github/copilot` on npm
- [OpenCode](https://opencode.ai/) - `opencode-ai` on npm
- [aider](https://aider.chat/) - aider's uv-based installer
- [Claude Code for VS Code](https://marketplace.visualstudio.com/items?itemName=anthropic.claude-code) - the editor extension
- [Node.js](https://nodejs.org/) - 24 LTS, installed as a tarball into the home directory
- [KasmVNC](https://github.com/kasmtech/KasmVNC) - the streaming layer
- [Caddy](https://caddyserver.com/) - the TLS-terminating proxy

### Implementation Details

**Signing in to each agent.** Every agent has a desktop icon that opens it in
a terminal. On first run each one offers its own login: Claude Code and Gemini
CLI open a browser page in Chrome inside the desktop, Codex offers ChatGPT
sign-in or a device code, Copilot CLI uses GitHub's device flow, and OpenCode
and aider read the same API keys or their own `auth` commands. Logins are
stored under the home directory on the volume, so you sign in once per agent.
An empty key variable is unset at boot so it can never shadow a browser login.

**Where the tools live.** Node.js is unpacked to `~/.local/node`, so the four
npm-installed agents and anything you `npm install -g` later land in
`~/.local/node/bin` and persist. Claude Code and aider install to
`~/.local/bin`. The boot script links every agent plus `node`, `npm` and `npx`
into `/usr/local/bin` so terminals and VS Code find them without any shell
profile, and appends both directories to `~/.bashrc`. The agents update
themselves, or with `npm update -g`.

**Volume budget.** The installed set is about 2.3 GB. npm and uv caches are
pointed at `/tmp` during installation and VS Code's cached extension downloads
are deleted, which keeps roughly 1.3 GB of throwaway data off the volume. Only
the Claude Code VS Code extension is preinstalled; the Codex and Gemini
extensions are 621 MB and 240 MB and duplicate their CLIs, and Copilot Chat is
built into VS Code. Install them from the marketplace if you want them. Grow
the volume in Railway if your repos need more room.

**VS Code.** The Claude Code extension does not run in Restricted Mode, so when
you open a folder for the first time, choose "Trust" in VS Code's workspace
prompt and its icon appears in the activity bar.

**Why there is a proxy service.** Kasm's startup script passes `-sslOnly` to
`vncserver`, so the desktop always serves HTTPS on 6901 and the documented
`network.ssl.require_ssl: false` setting is ignored. Railway speaks plain HTTP
to containers, so Caddy accepts Railway's HTTP and re-encrypts to Kasm,
skipping verification because the certificate is self-signed and regenerated on
every boot. Only the proxy has a public domain, and it carries the healthcheck.

**`VNC_PW` has a 6-character minimum.** KasmVNC's password tool silently
rejects anything shorter, which would leave the desktop unable to start and the
URL serving 502. The template validates the length at boot and fails with an
explicit message instead.

**Passwordless sudo is enabled deliberately.** Kasm ships `kasm-user` with a
locked password and no sudo rights, so a stock container cannot `apt install`
anything. A dev box needs that, so the template grants passwordless sudo at
boot. To lock it back down, remove `/etc/sudoers.d/kasm-user` inside the desktop.

**Security model.** `VNC_PW` is the only thing between the public Railway URL
and a root-capable desktop holding up to six agent logins. Set a strong one.
Anyone who reaches the URL with it can run anything as you, including every
agent against your accounts.

## Why Deploy Ubuntu AI Coding Agents Desktop on Railway?

Railway is a singular platform to deploy your infrastructure stack. Railway will host your infrastructure so you don't have to deal with configuration, while allowing you to vertically and horizontally scale it.

By deploying Ubuntu AI Coding Agents Desktop on Railway, you are one step closer to supporting a complete full-stack application with minimal burden. Host your servers, databases, AI agents, and more on Railway.


## Similar templates

- [Rocky Linux](https://railway.com/deploy/rocky-linux) — Hosted Rocky Linux 9 workspace with SSH and persistent storage. 🚀
- [Foundry Virtual Tabletop](https://railway.com/deploy/X5tR6G) — A Self-Hosted & Modern Roleplaying Platform
- [Letta Code Remote](https://railway.com/deploy/letta-code-remote) — Run a Letta Code agent 24/7. No inbound ports, just deploy.

Open this page in a browser: https://railway.com/deploy/ubuntu-ai-coding-agents-desktop-gui
