Deploying a Docker Application with Claude Code and Smoooth

This guide walks through deploying a containerized application with persistent PostgreSQL and automated SSL routing on an unmanaged Linux VPS using Claude Code and Smoooth. By the end of this tutorial, Claude Code will configure, provision, and verify a running Docker workload over the Model Context Protocol (MCP) without root shell access or raw Docker socket exposure.

The Architecture Problem: Raw Docker Socket vs. Scoped MCP

Developers currently instruct AI coding agents to manage infrastructure by providing SSH credentials or mounting /var/run/docker.sock directly into containerized coding environments. While functional for basic tasks, giving an autonomous LLM unconstrained root-equivalent Docker daemon access introduces serious hazards:

  • Root privilege escalation: Any process with Docker socket access can mount the host filesystem (-v /:/host) and modify system files.
  • No rollback state machine: If an ad-hoc docker run command fails halfway through, intermediate containers, orphan volumes, and colliding ports remain stranded.
  • Fragile secret management: Database passwords and API keys passed as CLI arguments leak into unencrypted shell history and host process lists (ps aux).

Smoooth solves this by running as a lightweight Rust daemon (consuming under 28MB RAM idle) bound exclusively to the private host loopback interface (127.0.0.1:4040). Claude Code communicates via typed MCP tools guarded by 256-bit cryptographic bearer tokens.

+-------------------------+ +---------------------------------------+ | Claude Code (Client) | | Linux Host VPS | | | | | | "Deploy Node.js API | | Smoooth Daemon (127.0.0.1:4040) | | with persistent DB" | | | +------------+------------+ +-------------------+-------------------+ | | | 1. MCP stdio / SSE (Bearer Token) | +---------------------------------------------------->| | | | 2. create_plan (Validates DAG & Hash) | |<----------------------------------------------------+ | | | 3. execute_plan (Applies transactional plan) | +---------------------------------------------------->+ | +--------------------------------------------+-----------------------+ | | | v v v +--------------------+ +-------------------+ +--------------------+ | Docker Supervisor | | Database Adapter | | Caddy Proxy | | Isolated container| | PostgreSQL 16 | | Automatic ACME TLS | | port 3000 | | /var/lib/smoooth/ | | https://api.domain | +--------------------+ +-------------------+ +--------------------+

Prerequisites

Before beginning, ensure you have the following:

  1. An unmanaged Linux VPS running Ubuntu 24.04 LTS or Debian 12 (1 vCPU, 1 GB RAM minimum).
  2. Docker Engine 24+ installed on the VPS (docker --version).
  3. Ports 80 and 443 open in your VPS firewall for public web ingress.
  4. Anthropic's Claude Code CLI installed on your workstation (npm install -g @anthropic-ai/claude-code).

Step 1: Install Smoooth on Your VPS

SSH into your target VPS and install the single static Smoooth binary. The installer registers a minimal systemd service with NoNewPrivileges=true:

Terminal (VPS SSH) bash
curl -fsSL https://smoooth.dev/install.sh | bash

Verify that the daemon is active and inspect its idle footprint:

Terminal (VPS SSH) bash
smoooth doctor

Expected output:

Output stdout
[✓] System: Linux 6.8.0-45-generic (x86_64)
[✓] Memory RSS: 24.8 MB (limit: 512 MB)
[✓] Docker Socket: /var/run/docker.sock (connected, v26.1.3)
[✓] Ingress: Caddy v2.8.4 (ready on :80, :443)
[✓] Loopback API: http://127.0.0.1:4040/health (200 OK)
[✓] Security Envelope: sandboxed (NoNewPrivileges, ProtectSystem=strict)

Now, generate a scoped 256-bit bearer token for Claude Code. We grant plan and deploy capabilities with a 30-day expiration:

Terminal (VPS SSH) bash
smoooth token create --scope plan,deploy --ttl 30d

Save the generated token (e.g., smt_live_8f39a0b4...). It will only be displayed once.

Step 2: Connect Claude Code to Smoooth over MCP

On your local development machine, configure Claude Code to register Smoooth as an active MCP server. You can connect securely over an SSH tunnel or directly via an HTTPS endpoint with bearer authentication:

Option A: Via Local SSH Forwarding (Recommended for homelabs and staging)

Terminal (Local Workstation) bash
# Establish tunnel from local 4040 to VPS loopback 4040
ssh -N -L 4040:127.0.0.1:4040 root@your-vps-ip &

# Register Smoooth in Claude Code
claude mcp add --transport sse smoooth http://127.0.0.1:4040/sse \
  --header "Authorization: Bearer smt_live_8f39a0b4..."

Option B: Via Stdio Over SSH (Zero Open Ports)

Claude Code can also invoke Smoooth directly over an SSH subprocess without exposing any HTTP port:

Terminal (Local Workstation) bash
claude mcp add --transport stdio smoooth -- ssh -q root@your-vps-ip "smoooth mcp"

Open a new session in Claude Code and run /mcp to verify the connection. You will see Smoooth's typed tools registered:

  • list_capabilities: Discovers verified adapters and provider constraints.
  • create_plan: Compiles and validates a 2-phase transactional DAG plan.
  • execute_plan: Provisions containers, volumes, and networks with fencing leases.
  • configure_ingress: Routes domains and provisions automatic TLS via Caddy.
  • verify_connection: Runs live TCP/HTTP probes confirming workload health.

Step 3: Deploying the Container Stack

With MCP configured, instruct Claude Code to deploy your workload in natural language:

Claude Code Prompt plaintext
Deploy our container image ghcr.io/myorg/api:v1.2.0 to my VPS.
Mount a persistent PostgreSQL 16 database, configure health probes, 
and route public traffic for api.example.com to port 3000 with automatic TLS.

What Claude Code Executes Under the Hood

Instead of guessing bash commands, Claude Code executes a verified sequence of typed MCP tool calls:

1. Tool Call: create_plan

Claude Code MCP Request json
{
  "workspace_id": "production",
  "spec": {
    "workloads": [
      {
        "name": "api-service",
        "image": "ghcr.io/myorg/api:v1.2.0",
        "port": 3000,
        "env": {
          "NODE_ENV": "production",
          "PORT": "3000"
        },
        "depends_on": ["pg-primary"]
      }
    ],
    "databases": [
      {
        "name": "pg-primary",
        "provider": "postgres",
        "version": "16-alpine",
        "database_name": "app_db",
        "persistent_volume": true
      }
    ],
    "ingress": {
      "domain": "api.example.com",
      "target_workload": "api-service",
      "target_port": 3000,
      "tls": true
    }
  }
}

Smoooth inspects the spec, checks host port availability, plans persistent storage under /var/lib/smoooth/volumes/production_pg_primary, and returns a verified plan with a canonical SHA-256 hash:

Smoooth MCP Response json
{
  "plan_id": "plan_9f8a2b3c4d5e",
  "canonical_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "risk_tier": "standard",
  "actions_count": 4,
  "estimated_ram_mb": 140,
  "status": "ready"
}

2. Tool Call: execute_plan

Claude Code invokes execution with an idempotency key to prevent accidental double-provisioning:

Claude Code MCP Request json
{
  "plan_id": "plan_9f8a2b3c4d5e",
  "idempotency_key": "deploy_20261023_01"
}

Smoooth provisions the PostgreSQL container, connects the shared Docker bridge network, injects the dynamic connection string (DATABASE_URL) into the application container, boots the API container, and configures Caddy's dynamic JSON config.

3. Tool Call: verify_connection

Smoooth automatically executes active probes:

  • TCP probe to PostgreSQL on port 5432 (passes in 12ms).
  • HTTP probe to http://127.0.0.1:<dynamic_port>/health (returns 200 OK).
  • ACME certificate issuance confirmation from Let's Encrypt for api.example.com.

Troubleshooting & Edge Cases

1. Transactional Rollback on Failed Health Checks

If your application crashes on startup (for example, missing a required runtime environment variable), Smoooth's 2-phase transactional DAG immediately triggers a rollback:

Automatic Rollback Guarantee: When health probes fail, Smoooth stops the newly launched container, restores previous network bindings, removes unverified Caddy routing rules, and preserves database volume state. Claude Code receives the exact container stderr trace to diagnose the issue immediately.

2. Handling Port Collisions

Traditional Docker scripts fail with bind: address already in use when port 3000 is occupied. Smoooth eliminates port collisions by decoupling internal container ports from host loopback bindings. Host ports are dynamically assigned from an ephemeral pool and mapped directly into Caddy's upstream proxy.

3. Permission Scopes

If Claude Code attempts an action exceeding its token scope (e.g. attempting to drop a volume with a read_only or plan token), Smoooth rejects the request with HTTP 403:

Security Audit json
{
  "error": "Forbidden",
  "message": "Token scope 'plan' does not permit action 'execute_plan'. Required: 'deploy'",
  "audit_event_id": "aud_7c29e1f4"
}

Comparison: Claude Code Deployment Approaches

Criteria Raw Docker Socket Mount Dokku / Coolify SSH Smoooth MCP
Protocol Raw UNIX Socket Raw Bash Shell (SSH) Model Context Protocol (JSON-RPC)
Security Boundary None (Host root access) Full user shell permissions 256-bit Scoped Tokens & systemd sandbox
Idle Memory Footprint Host Docker overhead 1.2 GB - 1.8 GB RAM (Coolify) < 28 MB RAM (Rust binary)
Rollback Support Manual bash cleanup Basic container restart 2-Phase Transactional DAG rollback
Database Persistence Ad-hoc volume paths Plugin-managed Deterministic volume mounts & health probes

For a deeper architectural breakdown of operational requirements and control plane designs, read our full Smoooth vs Coolify comparison.

Ready to give Claude Code infrastructure superpowers?

Deploy Smoooth on any Ubuntu or Debian VPS in under 30 seconds. Zero cloud lock-in, zero runtime bloat, and pure MCP control.