From bridgey-deploy
Deploy an agent to a remote server as a Docker container with Tailscale SSH access, optional Coolify integration, and bidirectional sync. Walks through server setup, hardening, Tailscale, Docker, deployment, and post-install tooling. Use when the user asks to deploy an agent remotely, set up a remote server, run an agent 24/7, or make an agent headless.
How this skill is triggered — by the user, by Claude, or both
Slash command
/bridgey-deploy:deployThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Deploy an agent to a remote server as a Docker container, accessible via Tailscale SSH with bidirectional sync and optional Coolify integration. This walkthrough is adaptive — it detects what's already done and skips completed steps.
Deploy an agent to a remote server as a Docker container, accessible via Tailscale SSH with bidirectional sync and optional Coolify integration. This walkthrough is adaptive — it detects what's already done and skips completed steps.
This skill needs access to ~/.personas/{name}/ (or the agent's local directory) to sync files to the remote server.
Ask the user which agent they want to deploy before starting.
Goal: Establish SSH access to a remote server.
Ask: "Do you already have a remote server, or do you need to set one up?"
If user needs a server:
If user has a server:
ssh -o ConnectTimeout=10 -o BatchMode=yes {user}@{ip} "echo ok"ssh {user}@{ip} "lsb_release -ds 2>/dev/null || cat /etc/os-release 2>/dev/null | head -3"Store for later phases: server IP, SSH username, OS info.
Goal: Ensure key-based SSH authentication works.
Detection: ssh -o BatchMode=yes {user}@{ip} exit 2>/dev/null
If key auth fails:
ls ~/.ssh/id_ed25519 ~/.ssh/id_rsa 2>/dev/nullssh-keygen -t ed25519 -C "{user_email}"ssh-copy-id {user}@{ip}ssh -o BatchMode=yes {user}@{ip} "echo key auth working"If key auth works: Report success, move on.
Goal: Basic security hardening — disable password auth, enable auto-updates.
Detection (via SSH):
ssh {user}@{ip} "grep -E '^PasswordAuthentication' /etc/ssh/sshd_config"ssh {user}@{ip} "dpkg -l unattended-upgrades 2>/dev/null | grep -q '^ii' && echo installed || echo missing"ssh {user}@{ip} "whoami" — if root, recommend creating a deploy userActions (only if needed):
ssh {user}@{ip} "sudo sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config && sudo systemctl restart sshd"
ssh {user}@{ip} "sudo apt-get update && sudo apt-get install -y unattended-upgrades && sudo dpkg-reconfigure -plow unattended-upgrades"
ssh root@{ip} "adduser --disabled-password --gecos '' deploy && usermod -aG sudo deploy && mkdir -p /home/deploy/.ssh && cp ~/.ssh/authorized_keys /home/deploy/.ssh/ && chown -R deploy:deploy /home/deploy/.ssh"
Then update stored SSH username to deploy.
Note: UFW firewall setup happens AFTER Phase 4 (Tailscale) to avoid locking ourselves out.
Goal: Set up Tailscale on the server for secure, zero-config networking.
Detection:
tailscale status 2>/dev/null — check if Tailscale is running locallyssh {user}@{ip} "tailscale status 2>/dev/null"If not installed on server:
ssh {user}@{ip} "curl -fsSL https://tailscale.com/install.sh | sh"ssh {user}@{ip} "sudo tailscale up --ssh"ssh {user}@{ip} "tailscale status"ssh {user}@{ip} "tailscale status --self --json | jq -r '.Self.DNSName' | sed 's/\.$//'"If already installed: Get Tailscale hostname, verify SSH works via Tailscale: ssh {tailscale_host} "echo tailscale ssh ok"
Post-Tailscale: Enable UFW firewall
Now that Tailscale SSH is confirmed working, lock down the server:
ssh {tailscale_host} "sudo ufw allow in on tailscale0 && sudo ufw --force enable && sudo ufw status"
This allows only Tailscale traffic — all public ports are blocked.
From this point on: Use {tailscale_host} for all SSH commands instead of the raw IP.
Store for later phases: Tailscale hostname.
Goal: Install Docker and build the agent container image.
Detection: ssh {tailscale_host} "docker --version 2>/dev/null"
If Docker not installed:
ssh {tailscale_host} "curl -fsSL https://get.docker.com | sh"
ssh {tailscale_host} "sudo usermod -aG docker {user} && newgrp docker"
ssh {tailscale_host} "docker run --rm hello-world"Build agent container:
ssh {tailscale_host} "mkdir -p /opt/bridgey/build"
${CLAUDE_PLUGIN_ROOT}/skills/deploy/references/Dockerfile and ${CLAUDE_PLUGIN_ROOT}/skills/deploy/references/entrypoint.sh, then transfer:scp ${CLAUDE_PLUGIN_ROOT}/skills/deploy/references/Dockerfile {tailscale_host}:/opt/bridgey/build/
scp ${CLAUDE_PLUGIN_ROOT}/skills/deploy/references/entrypoint.sh {tailscale_host}:/opt/bridgey/build/
ssh {tailscale_host} "cd /opt/bridgey/build && docker build -t bridgey-agent ."
This builds a shared image for all agents. Individual agents are differentiated by their docker-compose service config and environment variables.
Note: For existing deployments that used persona-{name} image names, the new bridgey-agent image supersedes them. Existing containers continue to work — only new deployments use the new image.
Goal: Sync agent files to server, set up auth, start the container.
Ask: "Which agent are you deploying? (e.g., julia, bob)"
Ask: "How do you want to deploy? (1) Docker Compose directly, or (2) via Coolify?"
ssh {tailscale_host} "sudo mkdir -p /opt/bridgey/personas/{name} /opt/bridgey/auth && sudo chown -R {user}:{user} /opt/bridgey"
Note: The path uses personas/ for backward compatibility with existing deployments. Fresh installs may use agents/ — ask the user if this is a new deployment or an existing one.
rsync -avz --exclude='.git' --exclude='*.log' --exclude='.mcp.json' --exclude='*.db' --exclude='*.db-journal' \
~/.personas/{name}/ {tailscale_host}:/opt/bridgey/personas/{name}/
scp ~/.claude/.credentials.json {tailscale_host}:/opt/bridgey/auth/
ssh {tailscale_host} "chmod 600 /opt/bridgey/auth/.credentials.json && chown 1000:1000 /opt/bridgey/auth/.credentials.json"
Security note: .credentials.json contains OAuth tokens. It's mounted read-only into the container, never baked into the image. If tokens expire, re-copy this file (/sync push does NOT sync credentials — this is intentional).
Read the template from ${CLAUDE_PLUGIN_ROOT}/skills/deploy/references/docker-compose.yml, replace {name} placeholders with the actual agent name, and write to /opt/bridgey/docker-compose.yml on the server.
If a docker-compose.yml already exists (from a previous deployment), merge the new service into the existing file rather than overwriting.
ssh {tailscale_host} "cd /opt/bridgey && docker compose up -d agent-{name}"
ssh {tailscale_host} "docker ps --filter name=agent-{name}"
ssh {tailscale_host} "docker exec agent-{name} claude -p 'respond with exactly: OK'"
Delegate to Skill('bridgey-deploy:coolify') — it handles service creation, env var configuration, and deployment via the Coolify API.
After the Coolify skill completes, return here for Phase 7 post-install steps.
Goal: Install sync/status tools into the agent and set up the remote shell alias.
Read ${CLAUDE_PLUGIN_ROOT}/skills/sync/SKILL.md as a template. The skill references bridgey-deploy.config.json for connection details. Write a copy with resolved placeholders to:
~/.personas/{name}/skills/remote/sync/SKILL.md
Read ${CLAUDE_PLUGIN_ROOT}/skills/remote-status/SKILL.md as a template. Write with resolved placeholders to:
~/.personas/{name}/skills/remote/status/SKILL.md
Read ${CLAUDE_PLUGIN_ROOT}/hooks/sync-reminder.json. Read the agent's existing ~/.personas/{name}/hooks.json. Append the sync reminder to the existing Stop array (don't replace the memory persistence hook that's already there). Write back the updated hooks.json.
Append to ~/.personas/.aliases.sh:
# Remote alias for {name} (via Tailscale SSH)
remote-{name}() {
if [ $# -eq 0 ]; then
ssh {tailscale_host} "cd /opt/bridgey/personas/{name} && claude"
else
ssh {tailscale_host} "cd /opt/bridgey/personas/{name} && claude -p \"\$*\""
fi
}
Create ~/.personas/{name}/bridgey-deploy.config.json (for skills to reference):
{
"tailscale_host": "{tailscale_host}",
"remote_path": "/opt/bridgey/personas/{name}",
"container_name": "agent-{name}",
"deploy_method": "compose|coolify",
"deployed_at": "{ISO timestamp}"
}
Add bridgey-deploy.config.json to the agent's .gitignore (contains host-specific info).
remote-{name} "respond with exactly: OK"Ask: "Does this agent need any ports open? (e.g., for bridgey daemon, dashboard, webhook listener)"
If yes, open each port only on the Tailscale interface — never publicly:
ssh {tailscale_host} "sudo ufw allow in on tailscale0 to any port {port}"
NEVER run ufw allow {port} without in on tailscale0 — that exposes the port to the public internet. All agent services should only be reachable via Tailscale.
Document any opened ports in bridgey-deploy.config.json:
{
"tailscale_host": "{tailscale_host}",
"remote_path": "/opt/bridgey/personas/{name}",
"container_name": "agent-{name}",
"deploy_method": "compose|coolify",
"deployed_at": "{ISO timestamp}",
"ports": [8080]
}
Print a completion summary:
remote-{name}, /sync, /remote-statusscp ~/.claude/.credentials.json {tailscale_host}:/opt/bridgey/auth/"Commit the new skills, hook changes, and config to the agent's git repo:
cd ~/.personas/{name}
git add skills/remote/ hooks.json bridgey-deploy.config.json .gitignore
git commit -m "feat({name}): add bridgey-deploy remote deployment"
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
npx claudepluginhub kickinrad/bridgey --plugin bridgey-deploy