This guide covers full machine setup (WSL + ttyd + Docker) and bringing the stack up for the first time.
cd /path/to/Tailshell
# Generate strong secrets (writes .env if missing)
bash ./scripts/generate-env
# Install/refresh ttyd wrapper + user systemd service
bash ./scripts/docker-setup
# Start the Docker stack (builds nginx image which includes the UI)
docker compose up -d --buildOptional (Docker secrets):
docker compose -f docker-compose.yml -f docker-compose.secrets.yml up -d --buildAccess: http://localhost:8081/ or https://<hostname>.<tailnet>.ts.net/
Bootstrap admin:
- Recommended: set
TAILSHELL_ADMIN_USERNAME/TAILSHELL_ADMIN_PASSWORDin.env - Otherwise: check
docker compose logs apifor a generated password on first run - On a fresh DB you’ll be redirected to
/change-passworduntil you rotate it
In Windows PowerShell:
wsl --install -d Ubuntu-24.04
wsl --update
wsl -l -vIn Ubuntu (WSL):
sudo sh -c 'echo -e "[boot]\nsystemd=true" > /etc/wsl.conf'In Windows PowerShell:
wsl --shutdownReopen Ubuntu and verify: ps -p 1 -o comm= should show systemd.
In Ubuntu (WSL):
sudo apt-get update && sudo apt-get -y upgrade
sudo apt-get install -y \
git curl ca-certificates openssl \
build-essential cmake \
libjson-c-dev libwebsockets-dev \
tmux ripgrep htop \
jqcd ~
git clone https://github.com/tsl0922/ttyd.git
cd ~/ttyd && git checkout 1.7.7
mkdir -p build && cd build
cmake .. && make -j"$(nproc)"
sudo make install
ttyd --version # Should show 1.7.7Follow Docker’s official Ubuntu guide or use Docker Desktop for Windows with WSL2 backend.
cd /path/to/Tailshell
bash ./scripts/generate-envIf you already have an existing MySQL Docker volume, changing MYSQL_* values later won’t automatically update the DB users. The fastest reset is:
docker compose down -v && docker compose up -d --buildbash ./scripts/docker-setupThis script:
- Installs the ttyd wrapper (
~/.local/bin/ai-ttyd-docker) - Installs a user-level systemd service (no root required for management)
- Enables user lingering (keeps service running after logout)
- Cleans up any old system-level services
- Starts the ttyd service
docker compose up -d
docker compose psExpected:
NAME STATUS PORTS
tailshell-mysql running (healthy) 127.0.0.1:3307->3306/tcp
tailshell-api running 127.0.0.1:3000->3000/tcp
tailshell-nginx running 127.0.0.1:8081->80/tcp
If you want nginx (not Tailscale) to terminate TLS and serve HTTP/2:
- Put your cert + key in
nginx/certs/as:nginx/certs/tls.crtnginx/certs/tls.key
- Set
TAILSHELL_COOKIE_SECURE=truein.env - Start the stack with the TLS override:
docker compose -f docker-compose.yml -f docker-compose.tls.yml up -d --buildAccess: https://localhost:8443/
Tailscale Serve is the recommended way to access this stack remotely over HTTPS inside your tailnet.
Notes:
- Avoid public exposure (don’t use Tailscale Funnel unless you explicitly want it public).
- Enabling HTTPS certificates issues trusted certs for your
*.ts.nethostname; your device name may appear in certificate transparency logs, so avoid sensitive names. - Tailscale Serve runs on Windows and proxies to Windows localhost. If
curl http://localhost:8081/doesn’t work in Windows, ensure WSL localhost forwarding is enabled (see below).
tailscale serve reset
tailscale serve --bg --yes 8081
tailscale serve statusIf Windows can’t reach the stack at http://localhost:8081/, ensure WSL localhost forwarding:
- Create/edit
%UserProfile%\.wslconfig:[wsl2] localhostForwarding=true
- Restart WSL:
wsl --shutdown
- Open
http://localhost:8081/(or your Tailscale URL) - Login with the bootstrap admin credentials (from
.envordocker compose logs api) - Complete
/change-passwordto continue (required on first login for a fresh DB)
This is only needed if you want to run these tools on the host. It requires Node.js.
# Claude Code
curl -fsSL https://claude.ai/install.sh | bash
# Codex CLI
npm i -g @openai/codexYou typically do not need these steps because scripts/docker-setup installs/updates them automatically.
The canonical entrypoint lives at scripts/ai-session and is installed by bash ./scripts/docker-setup.
If you change it, re-run bash ./scripts/docker-setup to reinstall and restart ttyd.
cat > ~/.tmux.conf <<'CONF'
set -g mouse on
set -g status off
set -g history-limit 50000
set -g alternate-screen off
set -ga terminal-overrides ",xterm*:smcup@:rmcup@"
CONFmkdir -p ~/.config/ai-webterm
cat > ~/.config/ai-webterm/ttyd.env <<'ENV'
TTYD_BIND=0.0.0.0
TTYD_PORT_INTERACTIVE=7681
ENV
chmod 600 ~/.config/ai-webterm/ttyd.env