<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrey Markin</title>
    <description>The latest articles on DEV Community by Andrey Markin (@andrey_markin).</description>
    <link>https://dev.to/andrey_markin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4071920%2Ff79d5290-4f63-4e62-bbd4-7760ca3a4d12.jpg</url>
      <title>DEV Community: Andrey Markin</title>
      <link>https://dev.to/andrey_markin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrey_markin"/>
    <language>en</language>
    <item>
      <title>The AI-Ready Web: Why Every Website Needs Both a UI and an API</title>
      <dc:creator>Andrey Markin</dc:creator>
      <pubDate>Wed, 12 Aug 2026 16:17:13 +0000</pubDate>
      <link>https://dev.to/andrey_markin/the-ai-ready-web-why-every-website-needs-both-a-ui-and-an-api-2idf</link>
      <guid>https://dev.to/andrey_markin/the-ai-ready-web-why-every-website-needs-both-a-ui-and-an-api-2idf</guid>
      <description>&lt;p&gt;Every website will soon serve two interfaces — one for humans, one for AI agents. Exploring WebMCP, MCP servers, generative UI, and the architectural shift making websites AI-accessible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7t348oe2nys0esg9zvs7.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7t348oe2nys0esg9zvs7.webp" alt="person standing between website UI and a programmatic UI" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Something strange is happening to the web. We're building a second version of it — not a redesign, not a rewrite, but an entirely parallel layer. One for humans, one for machines. And the interesting part? Most people haven't noticed yet.&lt;/p&gt;

&lt;p&gt;Right now, you're reading this article in a browser. You navigated here, scrolled through a page, and your eyes are parsing styled text against a background. That's the human web — the one we've known for thirty years.&lt;/p&gt;

&lt;p&gt;But there's another way to read this exact article. Add &lt;code&gt;.md&lt;/code&gt; to the URL. You'll get the same content, stripped down to plain markdown — no HTML, no styling, no JavaScript. Just structured text, ready to be consumed by an AI agent, a CLI tool, or whatever comes next.&lt;/p&gt;

&lt;p&gt;That's not a gimmick. That's a glimpse of where things are heading: every web service eventually serving two interfaces. The visual one you're used to, and a machine-readable one that most users will never see directly — but will interact with constantly through their AI assistants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Services Without Interfaces
&lt;/h2&gt;

&lt;p&gt;Rhys Sullivan, an engineer at OpenCode (previously at Vercel), &lt;a href="https://x.com/RhysSullivan/status/2022466168125267970" rel="noopener noreferrer"&gt;posted something&lt;/a&gt; that stuck with me:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By the end of the year, you are going to be interacting with most services through their APIs and your assistant rather than their websites. I think there will also be some services that are purely API only intended to be interacted with just by your assistant.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When I read this, my first reaction was practical — I'd need to rewrite my Server Actions into API endpoints. Rhys's answer was telling: use &lt;a href="https://orpc.unnoq.com/" rel="noopener noreferrer"&gt;oRPC&lt;/a&gt;, define your business logic once, and it works as both a Server Action (for your UI) and an OpenAPI endpoint (for agents). One source of truth, two consumers.&lt;/p&gt;

&lt;p&gt;Since then, Rhys has been building &lt;a href="https://github.com/RhysSullivan/executor" rel="noopener noreferrer"&gt;executor&lt;/a&gt; — a local-first execution environment that puts this idea into practice. Instead of pasting large MCP manifests into every chat or giving an agent broad shell access, executor provides a unified tool catalog across MCP servers, OpenAPI APIs, and GraphQL endpoints. The agent discovers capabilities by intent, inspects schemas, and calls typed functions in a sandboxed TypeScript runtime. It's the local control plane for the agent-mediated web — connect your sources once, and every agent interaction goes through a structured, credentialed runtime rather than raw HTTP calls.&lt;/p&gt;

&lt;p&gt;But the bigger point isn't about tooling. It's about the direction. We're moving from a world where the primary consumer of a web service is a human with a browser to one where it's an AI agent with an API client.&lt;/p&gt;

&lt;p&gt;This isn't hypothetical. Stripe already shipped an &lt;a href="https://stripe.com/blog/agentic-commerce-suite" rel="noopener noreferrer"&gt;Agentic Commerce Suite&lt;/a&gt; — a set of APIs designed specifically for AI agents to complete purchases autonomously. They co-developed an &lt;a href="https://github.com/agentic-commerce-protocol/agentic-commerce-protocol" rel="noopener noreferrer"&gt;Agentic Commerce Protocol&lt;/a&gt; with OpenAI. You can literally say "buy that" inside ChatGPT and Stripe handles the transaction end-to-end, no website involved.&lt;/p&gt;

&lt;p&gt;Cloudflare went even further. They shipped a &lt;a href="https://blog.cloudflare.com/code-mode-mcp/" rel="noopener noreferrer"&gt;Code Mode MCP server&lt;/a&gt; that exposes their entire platform — over 2,500 API endpoints — to AI agents. Not through a dashboard plugin or a browser extension. Through a server-side interface where the agent writes JavaScript against a typed API spec, executing in a sandboxed V8 isolate. Infrastructure management, fully agent-driven.&lt;/p&gt;

&lt;p&gt;The API-only business isn't a prediction. It's already shipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebMCP: The Right Idea, Wrong Layer?
&lt;/h2&gt;

&lt;p&gt;Google and Microsoft clearly see the same future. They've co-authored a &lt;a href="https://webmachinelearning.github.io/webmcp/" rel="noopener noreferrer"&gt;W3C proposal called WebMCP&lt;/a&gt; — a browser API that lets websites declare structured, callable tools for AI agents.&lt;/p&gt;

&lt;p&gt;The concept is elegant: instead of an AI agent screen-scraping your website or parsing DOM elements to figure out what buttons to click, your site explicitly tells the agent: "Here are the things you can do, here are the parameters, here's the schema." The agent calls a typed function. No pixel-parsing, no guesswork.&lt;/p&gt;

&lt;p&gt;I built a &lt;a href="https://github.com/Mark-Life/webMCP-example" rel="noopener noreferrer"&gt;demo project&lt;/a&gt; to test this — a task manager app using Next.js and oRPC, with WebMCP tool registration bridging the two. It works. Chrome 146 Canary already ships an early implementation behind a flag. You can install the &lt;a href="https://chromewebstore.google.com/detail/user-agent-mcp-bridge-mcb/jjjhcbnkhgagjckfpmmjmjddgoijkplm" rel="noopener noreferrer"&gt;MCP-B Chrome extension&lt;/a&gt; and interact with any WebMCP-enabled site through an AI agent.&lt;/p&gt;

&lt;p&gt;But here's my problem with it: &lt;strong&gt;WebMCP requires a browser.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agent must visit a rendered page to discover available tools. It runs within the browser's JavaScript context. This makes sense for one specific use case — agentic browsing, where an AI controls a browser tab on your behalf. But that's not how most agent interactions actually work or will work.&lt;/p&gt;

&lt;p&gt;Think about it. If you're asking Claude Code to interact with a service, there's no browser. If a background agent is processing your emails and booking meetings, there's no browser. If an API-only service exists purely for agent consumption — the kind Rhys predicts — there's no browser to render anything in.&lt;/p&gt;

&lt;p&gt;WebMCP solves a real problem (structured agent-website interaction) but anchors the solution to the wrong layer (the browser rendering engine). What agents actually need is simpler: a server-side endpoint that describes available capabilities and accepts typed calls. That's... just OpenAPI. Or an MCP server. We arguably already have the tools; we just need the adoption.&lt;/p&gt;

&lt;p&gt;It's telling that Cloudflare — a company that literally runs browser infrastructure — chose to go fully server-side with their agent interface. No browser rendering, no DOM, just typed API access in a sandbox. If anyone had reason to bet on the browser layer, it was them. They didn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Agentic Browser That Nobody Switched To
&lt;/h2&gt;

&lt;p&gt;There's a related observation that reinforces this. In 2025, we saw a wave of "agentic browsers" — Perplexity's Comet, OpenAI's Atlas, Microsoft Copilot in Edge. The pitch: your browser has a built-in AI that can navigate, fill forms, click buttons, and complete tasks for you.&lt;/p&gt;

&lt;p&gt;And yet... I haven't switched. You probably haven't either. I use Chrome on desktop, Safari on mobile — like most people. When I need an agent to interact with the web, I use tools designed for it — direct API calls, MCP servers, or simple HTTP requests to structured endpoints.&lt;/p&gt;

&lt;p&gt;The reason is subtle but important. Agentic browsing treats the existing visual web as the source of truth and puts an AI layer on top of it to interpret and navigate that visual layer. It's a workaround. It says: "The web was built for humans, so let's teach an AI to pretend to be a human using it."&lt;/p&gt;

&lt;p&gt;That's the wrong abstraction. Instead of teaching agents to navigate human interfaces, we should give agents their own interfaces to the same services. The data and capabilities are the same — the access layer is different.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works Today
&lt;/h2&gt;

&lt;p&gt;Here's what I've found actually works for making a web service agent-accessible, without waiting for standards bodies or new browsers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For content-heavy sites:&lt;/strong&gt; serve a markdown version of every page. On my website, adding &lt;code&gt;.md&lt;/code&gt; or &lt;code&gt;.txt&lt;/code&gt; to any URL returns clean markdown. Sending an &lt;code&gt;Accept: text/markdown&lt;/code&gt; header does the same. This is trivially simple to implement, requires zero special infrastructure, and works with every AI tool that can make HTTP requests.&lt;/p&gt;

&lt;p&gt;It also enables concrete features for human users: "Copy as Markdown" and "Open in ChatGPT" buttons on every page. The agent layer and the human layer share the same content, accessed differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For application-heavy sites:&lt;/strong&gt; expose your business logic as an OpenAPI spec. Tools like oRPC let you define a procedure once and use it as both a Server Action (for your React UI) and as a documented API endpoint (for agents). The agent doesn't need to "visit" your site — it reads your API spec, understands your capabilities, and calls them directly.&lt;/p&gt;

&lt;p&gt;A good example of this in the wild: &lt;a href="https://flashcards-open-source-app.com/" rel="noopener noreferrer"&gt;Flashcards&lt;/a&gt;, an open-source spaced repetition app built by &lt;a href="https://github.com/kirill-markin" rel="noopener noreferrer"&gt;Kirill Markin&lt;/a&gt;. It exposes a &lt;a href="https://api.flashcards-open-source-app.com/v1/" rel="noopener noreferrer"&gt;discovery endpoint&lt;/a&gt; that gives an AI agent everything it needs — authentication flow, available capabilities, OpenAPI spec — to sign up, create an account, and manage flashcard workspaces autonomously. You give your agent the discovery URL, confirm an email code, and it handles the rest. This is what an agent-first API looks like in practice: the web UI exists, but the API is the primary interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For discoverability:&lt;/strong&gt; this is where it gets interesting, because the right approach depends on scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Discovery Problem: Scale Changes Everything
&lt;/h2&gt;

&lt;p&gt;Making your API agent-accessible sounds simple — just expose a spec. But in practice, there's a spectrum, and the right pattern depends on how many capabilities you're exposing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Small surface (5–15 tools):&lt;/strong&gt; Most web apps fall here. A task manager, a recipe site, a SaaS dashboard. For these, the agent can consume the entire API spec in one shot and generate typed tools from it. This is the pattern I'm exploring with &lt;a href="https://github.com/Mark-Life/OpenTools" rel="noopener noreferrer"&gt;OpenTools&lt;/a&gt; — a specification that extends OpenAPI with &lt;code&gt;x-llm&lt;/code&gt; metadata (approval requirements, rate limits, natural-language hints for when to use each action) and makes the whole thing auto-discoverable via a &lt;code&gt;/.well-known/llm.json&lt;/code&gt; endpoint. The agent fetches the manifest, reads the spec, generates tools dynamically, and starts calling them — no MCP server setup, no plugin installation, just a URL. Think of it as &lt;code&gt;robots.txt&lt;/code&gt; for AI agents, but instead of telling crawlers what to index, it tells agents what they can &lt;em&gt;do&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Large surface (hundreds or thousands of endpoints):&lt;/strong&gt; Here, the "just read the spec" approach breaks down. Cloudflare learned this firsthand — their API has over 2,500 endpoints, which would consume 1.17 million tokens as a raw OpenAPI spec. No model can hold that in context. Their solution was &lt;a href="https://blog.cloudflare.com/code-mode-mcp/" rel="noopener noreferrer"&gt;Code Mode&lt;/a&gt;: instead of exposing thousands of individual tools, they give the agent just two meta-tools — &lt;code&gt;search()&lt;/code&gt; to query the spec and find relevant endpoints, and &lt;code&gt;execute()&lt;/code&gt; to call them. The agent discovers capabilities progressively, writing JavaScript against a typed API rather than consuming the whole schema upfront. Fixed token cost regardless of API size.&lt;/p&gt;

&lt;p&gt;These aren't competing patterns — they're complementary. For most web applications, direct spec consumption works fine and keeps things simple. For platforms with massive API surfaces, progressive discovery is the way. Either way, the solution is server-side, typed, and doesn't require a browser.&lt;/p&gt;

&lt;p&gt;These also aren't futuristic proposals. They're patterns you can implement today with existing tools. And they work with every type of agent — browser-based, CLI-based, embedded in chat interfaces, or running autonomously in the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Output Side: Generative UI
&lt;/h2&gt;

&lt;p&gt;So far I've been talking about the input side — how agents access services. But there's an equally interesting shift happening on the output side: how agents present results to users.&lt;/p&gt;

&lt;p&gt;Vercel Labs recently released &lt;a href="https://github.com/vercel-labs/json-render" rel="noopener noreferrer"&gt;json-render&lt;/a&gt;, a framework by Chris Tate where AI generates constrained JSON specs that map to pre-defined UI components. The developer defines a catalog of allowed components (cards, tables, charts, metrics) with typed props. The LLM composes from that vocabulary. A renderer turns the JSON into actual React (or React Native) components.&lt;/p&gt;

&lt;p&gt;This is significant because it reframes who designs the UI. Traditionally, every user sees the same dashboard, the same layout, the same arrangement of information. With generative UI, the interface adapts to the user's specific question or context. Ask for revenue data, and you get a dashboard with charts. Ask for a customer list, and you get a filterable table. The same backend, rendered differently each time.&lt;/p&gt;

&lt;p&gt;But there's a practical constraint worth thinking about: token cost. If you want to render a table with 500 rows, you don't want the LLM generating that data token by token. The smart pattern is for the model to generate &lt;em&gt;structure&lt;/em&gt; — "render a table component with this data source" — and let the client-side renderer handle the actual data population. Pass a reference to the data, not the data itself.&lt;/p&gt;

&lt;p&gt;This is where json-render's approach shines. The LLM produces a lightweight JSON spec — maybe 2,000 tokens — that describes &lt;em&gt;what&lt;/em&gt; to render. The heavy lifting (fetching data, rendering components, handling interactions) happens locally, outside the generation loop.&lt;/p&gt;

&lt;p&gt;Google is working on something similar with &lt;a href="https://github.com/google/A2UI" rel="noopener noreferrer"&gt;A2UI&lt;/a&gt; — a protocol for agents to describe UI across trust boundaries. The difference: json-render is a tool (AI as a feature inside your app), while A2UI is a protocol (AI as a remote entity generating UI for a host application). Both point to the same future.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Duality
&lt;/h2&gt;

&lt;p&gt;Here's where I'm genuinely uncertain, and I think that uncertainty is worth admitting.&lt;/p&gt;

&lt;p&gt;On one hand, the trajectory seems clear: agents will increasingly mediate our interactions with online services. The protocols are emerging (MCP, WebMCP, A2A, ACP). The infrastructure is being built (oRPC, json-render, agentic commerce APIs). Major companies are betting on it.&lt;/p&gt;

&lt;p&gt;On the other hand... people still use regular websites. I still use regular websites. Muscle memory is powerful. When I want to check my bank balance, I open the bank's app — I don't ask an AI to query their API. When I want to buy something on Amazon, I browse. The behavioral shift required for the "agent-first" web is enormous, and humans change habits slowly.&lt;/p&gt;

&lt;p&gt;I think what we'll see isn't a replacement but a stratification. Some interactions will move to agents quickly — repetitive tasks, multi-service workflows, data retrieval. Others will stay visual for a long time — browsing for inspiration, social media, content consumption where the UI &lt;em&gt;is&lt;/em&gt; the experience.&lt;/p&gt;

&lt;p&gt;The implication for builders is that you need to serve both. Your website still needs to be a great visual experience for humans. But it also needs a machine-readable layer — API endpoints, structured data, markdown export, capability declarations — so that when someone's AI assistant comes knocking, it can interact with your service without pretending to be a human clicking buttons.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Developers
&lt;/h2&gt;

&lt;p&gt;If you're building web applications today, the practical takeaway is straightforward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Think in two layers.&lt;/strong&gt; Your visual UI layer serves humans. Your API/data layer serves both humans and agents. Design the API layer first, then build the UI on top of it. This is good architecture regardless of the agent trend — but the agent trend makes it mandatory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expose your capabilities.&lt;/strong&gt; If your app has functionality, make it callable. For most apps, an OpenAPI spec with agent metadata at a well-known URL is enough — agents can consume it directly and generate tools. For large platforms, consider progressive discovery patterns like Cloudflare's Code Mode. Either way, the businesses that are machine-discoverable and machine-callable will have a massive advantage — the same way SEO-optimized sites had an advantage when Google became the primary way people found things online.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't over-invest in agentic browser compatibility.&lt;/strong&gt; WebMCP is interesting to watch but the spec is unstable and the browser-centric approach has fundamental limitations. Your time is better spent making your server-side APIs clean and well-documented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider your content.&lt;/strong&gt; If your site has valuable content (articles, documentation, product information), serve it in plain text or markdown alongside the HTML version. It's simple, it costs nothing, and it makes your content immediately accessible to every AI tool on the market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I Think This Goes
&lt;/h2&gt;

&lt;p&gt;I don't think we end up with one AI assistant that rules everything. We'll end up with something closer to the social media landscape — people use different platforms for different purposes. TikTok for entertainment, LinkedIn for professional networking, Twitter for real-time discourse. AI assistants will stratify the same way. You might use Claude for coding, ChatGPT for general tasks, Gemini for things deeply integrated with Google's ecosystem. People will have preferences, loyalties, workflows built around specific providers. The "one agent to rule them all" scenario is as unlikely as "one social network for everything" — we've seen how that plays out.&lt;/p&gt;

&lt;p&gt;In the near term, the shift is clearest for transactional interactions. Anything you do today that involves opening an app, clicking through a workflow, and closing it — checking a balance, booking a flight, filing an expense, managing a subscription — will move to agents. Nobody enjoys those UIs. They exist because there was no alternative. Now there is.&lt;/p&gt;

&lt;p&gt;The experience-first web survives. Browsing for inspiration, consuming content, social media — these stay visual because the interface &lt;em&gt;is&lt;/em&gt; the product. You don't ask an agent to scroll Instagram for you. The utilitarian middle is what gets eaten.&lt;/p&gt;

&lt;p&gt;But if I'm being honest about the longer arc, there's a more radical disruption waiting. We're currently bolting agent capabilities onto existing systems — adding API layers to websites, connecting assistants to services through protocols. The infrastructure of the web, the browser, the operating system — all of it was designed around the assumption that a human is directly manipulating a visual interface.&lt;/p&gt;

&lt;p&gt;What happens when someone builds an operating system that doesn't make that assumption? An OS where the primary interface is an LLM-driven agent, where UI is generative — composed on the fly from the user's intent rather than pre-designed by a product team, where every application is just a set of callable capabilities that the system orchestrates and renders as needed. Not an AI layer on top of a traditional OS, but an OS built from the ground up around the agent paradigm.&lt;/p&gt;

&lt;p&gt;That's the real disruption. Everything I've described in this article — the dual interfaces, the discovery protocols, the progressive API access — is a transition pattern. It's how we get from the human-first web to something fundamentally different. The question isn't whether it happens, but whether the transition is gradual enough for people to adapt, or whether someone ships the paradigm shift all at once and forces the industry to catch up.&lt;/p&gt;

&lt;p&gt;Either way, the web isn't dying. It's growing a second nervous system — one designed for machines. The sites that develop both will thrive. The ones that remain human-only will gradually become invisible to an increasingly agent-mediated world.&lt;/p&gt;

&lt;p&gt;That's not a threat. It's just the next layer.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Claude Code on VPS: Full Setup to Code from Your Phone</title>
      <dc:creator>Andrey Markin</dc:creator>
      <pubDate>Mon, 10 Aug 2026 20:13:51 +0000</pubDate>
      <link>https://dev.to/andrey_markin/claude-code-on-vps-full-setup-to-code-from-your-phone-5hhp</link>
      <guid>https://dev.to/andrey_markin/claude-code-on-vps-full-setup-to-code-from-your-phone-5hhp</guid>
      <description>&lt;p&gt;Complete guide to setting up Claude Code on a private VPS with Tailscale security, Tmux persistence, and Caddy HTTPS – code from anywhere, even your phone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5uurmoze85ev60y7pw7i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5uurmoze85ev60y7pw7i.webp" alt="Abstraction image representing mobile phone and secure connection to the server" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;I wanted to code from my phone. Not "review PRs" or "edit a config file" – actually run Claude Code, iterate on features, and preview changes in the browser. Turns out a $7/mo VPS is all you need.&lt;/p&gt;

&lt;p&gt;This guide covers the full stack: Hetzner VPS, Tailscale for zero-trust SSH, Tmux for persistent sessions, and Caddy for HTTPS dev server access. By the end you'll have a setup where you SSH in from your phone, attach a Tmux session with Claude Code already running, and preview your app at &lt;code&gt;https://dev.yourdomain.com&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdv96gqn28f17s59mmcdu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdv96gqn28f17s59mmcdu.png" alt="Claude Code on VPS with Tailscale architecture mermaid diagram" width="800" height="759"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The setup has 5 moving parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hetzner VPS&lt;/strong&gt; – cheap, fast, EU/US datacenters. The machine where everything runs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailscale&lt;/strong&gt; – mesh VPN. SSH traffic goes over encrypted Tailscale tunnel, never over public internet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminus&lt;/strong&gt; – SSH client for iOS/Android/desktop. Connects to VPS via Tailscale IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tmux&lt;/strong&gt; – terminal multiplexer. Sessions persist when you disconnect. Phone goes to sleep? Claude keeps working&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caddy&lt;/strong&gt; – reverse proxy with automatic HTTPS. Exposes &lt;code&gt;localhost:3000&lt;/code&gt; at &lt;code&gt;https://dev.yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  VPS Setup
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://www.hetzner.com/cloud/" rel="noopener noreferrer"&gt;Hetzner Cloud&lt;/a&gt;, create a project, spin up a server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Location&lt;/strong&gt;: closest datacenter to you&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image&lt;/strong&gt;: Latest Ubuntu&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type&lt;/strong&gt;: min: CX22 (2 vCPU, 4GB RAM, IPv4) – ~$7/mo, enough for me, but you choose you. If you run big projects and lots of parallel Claude instances, i would advise you get more memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hetzner sends the root password to your email. SSH in as root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@&amp;lt;your-server-ip&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be prompted to change the root password immediately on first login.&lt;/p&gt;

&lt;h3&gt;
  
  
  Non-Root User
&lt;/h3&gt;

&lt;p&gt;Claude Code with &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; refuses to run as root. Create a dedicated user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser agent
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;agent

&lt;span class="c"&gt;# Copy your SSH key to the new user&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/agent/.ssh
&lt;span class="nb"&gt;cp&lt;/span&gt; ~/.ssh/authorized_keys /home/agent/.ssh/
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; agent:agent /home/agent/.ssh
&lt;span class="nb"&gt;chmod &lt;/span&gt;700 /home/agent/.ssh
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /home/agent/.ssh/authorized_keys

&lt;span class="c"&gt;# Switch to agent&lt;/span&gt;
su - agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From now on, everything runs as &lt;code&gt;agent&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;

&lt;p&gt;Install Claude Code early – it can help you configure everything else on the server (Tailscale, firewall, shell, tooling, etc.).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Claude Code&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://claude.ai/install.sh | bash

&lt;span class="c"&gt;# First run – authenticates via browser&lt;/span&gt;
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the auth link. Once authenticated, you can use Claude Code to help with the remaining setup steps – or follow the guide manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tailscale
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt; creates a zero-trust mesh VPN between your devices. Free for personal use (up to 100 devices). Every device gets a stable &lt;code&gt;100.x.x.x&lt;/code&gt; IP that works everywhere.&lt;/p&gt;

&lt;p&gt;Install on VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://tailscale.com/install.sh | sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;tailscale up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the auth URL to link the VPS to your Tailscale account. Note the &lt;code&gt;100.x.x.x&lt;/code&gt; IP – that's your SSH target from now on.&lt;/p&gt;

&lt;p&gt;Install Tailscale on your phone/desktop too. Once both devices are on the same Tailnet, they can reach each other directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Firewall (ufw)
&lt;/h3&gt;

&lt;p&gt;Lock down the VPS. Only expose what's needed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ufw&lt;/code&gt; is just a friendly front-end for the kernel's netfilter firewall – it isn't included on many minimal/VPS base images, so install it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ufw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On other distros: &lt;code&gt;sudo dnf install -y firewalld&lt;/code&gt; on RHEL/Fedora/CentOS/Rocky/Alma (or install &lt;code&gt;ufw&lt;/code&gt; from EPEL), &lt;code&gt;sudo apk add ufw&lt;/code&gt; on Alpine. The walkthrough below assumes Ubuntu/Debian.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lockout warning&lt;/strong&gt;: the rule below allows SSH &lt;strong&gt;only&lt;/strong&gt; over the &lt;code&gt;tailscale0&lt;/code&gt; interface. If Tailscale isn't connected when you enable the firewall, you'll be locked out of the VPS. Before running &lt;code&gt;sudo ufw enable&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;ufw&lt;/code&gt; (above)&lt;/li&gt;
&lt;li&gt;Confirm Tailscale is up: &lt;code&gt;tailscale status&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open a second SSH session to the Tailscale IP (&lt;code&gt;100.x.x.x&lt;/code&gt;) and keep it open – it's your safety net if a rule is wrong&lt;/li&gt;
&lt;li&gt;Add the rules&lt;/li&gt;
&lt;li&gt;Then enable the firewall
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Allow SSH only over Tailscale interface&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow &lt;span class="k"&gt;in &lt;/span&gt;on tailscale0 to any port 22

&lt;span class="c"&gt;# Allow HTTPS from anywhere (for Caddy dev server)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw allow 443/tcp

&lt;span class="c"&gt;# Enable firewall&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw &lt;span class="nb"&gt;enable&lt;/span&gt;

&lt;span class="c"&gt;# Verify&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ufw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: SSH is only accessible via Tailscale tunnel. Port 443 is open for the HTTPS dev server. Everything else is blocked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Terminus SSH Client
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://termius.com/" rel="noopener noreferrer"&gt;Terminus&lt;/a&gt; is a cross-platform SSH client – works on iOS, Android, macOS, Windows. It's the best mobile SSH experience I've found.&lt;/p&gt;

&lt;p&gt;Setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install Terminus on your phone and/or desktop&lt;/li&gt;
&lt;li&gt;Create a new host:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Address&lt;/strong&gt;: your VPS Tailscale IP (&lt;code&gt;100.x.x.x&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Username&lt;/strong&gt;: &lt;code&gt;agent&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt;: SSH key (import your private key)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Connect – you're in&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Tmux
&lt;/h2&gt;

&lt;p&gt;This is the critical piece. Without Tmux, closing your SSH connection (or your phone going to sleep) kills whatever's running. With Tmux, processes keep running on the server – you just detach and reattach.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; tmux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5ggw68mhwokrx5csb8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr5ggw68mhwokrx5csb8e.png" alt="Tmux resumable sessions mermaid diagram" width="800" height="1382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Essential Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create named session (name it after your project)&lt;/span&gt;
tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; project1

&lt;span class="c"&gt;# Detach from session (keeps it running)&lt;/span&gt;
&lt;span class="c"&gt;# Ctrl+B, then D&lt;/span&gt;

&lt;span class="c"&gt;# List sessions&lt;/span&gt;
tmux &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# Attach to existing session&lt;/span&gt;
tmux a &lt;span class="nt"&gt;-t&lt;/span&gt; project1

&lt;span class="c"&gt;# Kill a session&lt;/span&gt;
tmux kill-session &lt;span class="nt"&gt;-t&lt;/span&gt; project1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Parallel Workflow
&lt;/h3&gt;

&lt;p&gt;Use one tmux session per project. Claude Code starts the dev server itself (in a background tmux session or via its built-in tools), so you just need one session to interact with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Project 1: your main project&lt;/span&gt;
tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; project1
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/project1
claude

&lt;span class="c"&gt;# Detach: Ctrl+B, D&lt;/span&gt;

&lt;span class="c"&gt;# Project 2: another project&lt;/span&gt;
tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; project2
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/project2
claude

&lt;span class="c"&gt;# Detach: Ctrl+B, D&lt;/span&gt;

&lt;span class="c"&gt;# Switch between projects:&lt;/span&gt;
tmux a &lt;span class="nt"&gt;-t&lt;/span&gt; project1
tmux a &lt;span class="nt"&gt;-t&lt;/span&gt; project2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each project gets its own session. Claude handles dev servers, builds, and other processes within the session. Switch between projects as needed – everything survives SSH disconnects.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Parallel changes on the same repo?&lt;/strong&gt; Use &lt;a href="https://git-scm.com/docs/git-worktree" rel="noopener noreferrer"&gt;git worktrees&lt;/a&gt;. Worktrees let you check out multiple branches of the same repo into separate directories, so two Claude instances can work on different features simultaneously without conflicts:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/project1
git worktree add ../project1-feature-b feature-b
&lt;span class="c"&gt;# Now run a separate tmux session for the worktree&lt;/span&gt;
tmux new &lt;span class="nt"&gt;-s&lt;/span&gt; project1-feature-b
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/project1-feature-b
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;Optional but recommended &lt;code&gt;.tmux.conf&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.tmux.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Mouse support (useful for scrolling)&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; mouse on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload with &lt;code&gt;tmux source-file ~/.tmux.conf&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shell &amp;amp; Tooling
&lt;/h2&gt;

&lt;p&gt;Install the tools you'll need on the VPS. Here's what I'd recommend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;zsh&lt;/strong&gt; + oh-my-zsh – better shell experience, autocompletions, plugins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;git&lt;/strong&gt; – version control, obviously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;gh&lt;/strong&gt; (GitHub CLI) – create PRs, open issues, review checks, all from the terminal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;docker&lt;/strong&gt; – if you run database instances, Redis, or other services locally&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;nvim&lt;/strong&gt; – if you want to edit files in vim mode directly on the server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dev runtimes&lt;/strong&gt; – depends on your stack: bun, nvm (Node), python, uv, go, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once your shell is set up, add this alias to your &lt;code&gt;.zshrc&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.zshrc&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;cc&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'claude --dangerously-skip-permissions'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs Claude Code in fully autonomous mode – no approval prompts for commands or file changes. Much better for async workflows where you kick off a task and come back later. But be cautious: in this mode Claude can do anything without asking, including pushing to main, running database migrations, deleting files, etc. Use it when you trust the task scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Config
&lt;/h2&gt;

&lt;p&gt;Set up &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; so Claude Code understands your preferences from the start. This global config applies to all projects:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Claude Code Rules&lt;/span&gt;

&lt;span class="gu"&gt;## Environment&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Platform**&lt;/span&gt;: VPS accessed via SSH from multiple devices
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Shell**&lt;/span&gt;: zsh
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Session management**&lt;/span&gt;: tmux (always assume running inside tmux)

&lt;span class="gu"&gt;## Communication Style&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Concise and direct response. Sacrifice grammar for the sake of concision.
&lt;span class="p"&gt;-&lt;/span&gt; Minimal explanation unless asked
&lt;span class="p"&gt;-&lt;/span&gt; No emojis

&lt;span class="gu"&gt;## Tmux Workflow&lt;/span&gt;
Since this is a VPS environment with SSH access from multiple devices, use tmux sessions for process isolation:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Long-running processes**&lt;/span&gt; (dev servers, watchers, builds): Create new tmux sessions
  &lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;  tmux new-session &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &amp;lt;name&amp;gt; &lt;span class="s1"&gt;'&amp;lt;command&amp;gt;'&lt;/span&gt;
  &lt;span class="p"&gt;```&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Naming convention**&lt;/span&gt;: &lt;span class="sb"&gt;`{project}-{purpose}`&lt;/span&gt; (e.g., &lt;span class="sb"&gt;`myapp-dev`&lt;/span&gt;, &lt;span class="sb"&gt;`api-build`&lt;/span&gt;) - prefix with repo/project name to avoid collisions across parallel instances
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Checking output**&lt;/span&gt;: Use &lt;span class="sb"&gt;`tmux capture-pane -t &amp;lt;session&amp;gt; -p`&lt;/span&gt; to read session output
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Listing sessions**&lt;/span&gt;: &lt;span class="sb"&gt;`tmux list-sessions`&lt;/span&gt;

This allows reconnecting from any device and finding all processes still running in their respective sessions.

&lt;span class="gu"&gt;## Tools Available&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; git, gh (GitHub CLI)
&lt;span class="p"&gt;-&lt;/span&gt; Standard Unix tools
&lt;span class="p"&gt;-&lt;/span&gt; oh-my-zsh

&lt;span class="gu"&gt;## Plan Mode&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Make the plan extremely concise. Sacrifice grammar for the sake of concision.
&lt;span class="p"&gt;-&lt;/span&gt; At the end of each plan, give me a list of unresolved questions to answer, if any.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;You can adjust Tools Available section to include your coding environment like Bun, UV, etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key VPS-specific addition is the &lt;strong&gt;Tmux Workflow&lt;/strong&gt; section – it tells Claude Code to spin up long-running processes in separate tmux sessions instead of blocking the current terminal. This way Claude creates &lt;code&gt;myapp-dev&lt;/code&gt; sessions for dev servers, and you can check their output or reattach from any device.&lt;/p&gt;

&lt;p&gt;You can also add per-project &lt;code&gt;CLAUDE.md&lt;/code&gt; files in each repo root for project-specific commands, architecture notes, and patterns. Claude Code reads both files automatically on startup. The easiest way to call /init inside Claude Code running in the project folder.&lt;/p&gt;




&lt;h2&gt;
  
  
  HTTPS Dev Server
&lt;/h2&gt;

&lt;p&gt;Problem: Claude Code starts &lt;code&gt;bun dev&lt;/code&gt; on &lt;code&gt;localhost:3000&lt;/code&gt;, but you can't hit localhost from your phone browser. Solution: Caddy reverse proxy with automatic SSL.&lt;/p&gt;

&lt;h3&gt;
  
  
  DNS Setup
&lt;/h3&gt;

&lt;p&gt;In your domain's DNS settings, add an A record:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;DNS Record&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Type: A
Name: dev
Value: &amp;lt;your-vps-public-ip&amp;gt;
TTL: 300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This points &lt;code&gt;dev.yourdomain.com&lt;/code&gt; to your VPS public IP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caddy Setup
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://caddyserver.com/" rel="noopener noreferrer"&gt;Caddy&lt;/a&gt; handles HTTPS automatically – it provisions Let's Encrypt certificates with zero config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Caddy&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; debian-keyring debian-archive-keyring apt-transport-https curl
curl &lt;span class="nt"&gt;-1sLf&lt;/span&gt; &lt;span class="s1"&gt;'https://dl.cloudsmith.io/public/caddy/stable/gpg.key'&lt;/span&gt; | &lt;span class="nb"&gt;sudo &lt;/span&gt;gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl &lt;span class="nt"&gt;-1sLf&lt;/span&gt; &lt;span class="s1"&gt;'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/caddy-stable.list
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;caddy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure the Caddyfile:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/etc/caddy/Caddyfile&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dev.yourdomain.com {
    reverse_proxy localhost:3000
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire config. Caddy auto-provisions SSL and reverse-proxies to your dev server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Restart Caddy to pick up changes&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart caddy

&lt;span class="c"&gt;# Check status&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status caddy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when Claude starts &lt;code&gt;bun dev&lt;/code&gt; in a Tmux session, open &lt;code&gt;https://dev.yourdomain.com&lt;/code&gt; on your phone – live preview of your app with hot reload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security note&lt;/strong&gt;: only port 443 is publicly exposed (for the HTTPS dev server). SSH stays locked behind Tailscale. If you don't need public HTTPS access, you can skip Caddy entirely and access the dev server over Tailscale too (&lt;code&gt;http://100.x.x.x:3000&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Workflow
&lt;/h2&gt;

&lt;p&gt;End-to-end, here's what coding from your phone looks like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Open Terminus&lt;/strong&gt; on your phone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH into VPS&lt;/strong&gt; via Tailscale IP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attach Tmux session&lt;/strong&gt;: &lt;code&gt;tmux a -t project1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Talk to Claude Code&lt;/strong&gt; – describe the feature, fix, or change&lt;/li&gt;
&lt;li&gt;You can close the app and break the connection. Claude edits files, runs commands, iterates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview changes&lt;/strong&gt;: open &lt;code&gt;https://dev.yourdomain.com&lt;/code&gt; in phone browser&lt;/li&gt;
&lt;li&gt;Sessions keep running – come back anytime from any device&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also run this from desktop with the same setup. The VPS becomes your persistent dev environment that you access from anywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Telegram Bot Interface
&lt;/h2&gt;

&lt;p&gt;I've been building a &lt;a href="https://github.com/Mark-Life/telegram-claude" rel="noopener noreferrer"&gt;Telegram bot&lt;/a&gt; that acts as a chat interface to Claude Code on the VPS. Instead of opening an SSH client, you just message the bot from Telegram – it spawns Claude Code in the selected project directory and streams results back.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa1zouc1ejj8izqa8jbma.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa1zouc1ejj8izqa8jbma.png" alt="telegram client to server to claude relation digram" width="800" height="139"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Key features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Project switching&lt;/strong&gt; – select which project directory to work in via inline keyboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice messages&lt;/strong&gt; – record a voice note describing what you want, the bot transcribes it (via Groq Whisper) and feeds it to Claude Code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming responses&lt;/strong&gt; – live-updated Telegram messages as Claude works&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversation continuity&lt;/strong&gt; – follow-up messages reuse the same Claude session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost tracking&lt;/strong&gt; – each response shows token cost and duration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow becomes: open Telegram, pick a project, describe a feature or fix (type or voice), and Claude Code does the work – edits files, runs commands, creates PRs. You get the output streamed back as Telegram messages.&lt;/p&gt;

&lt;p&gt;It's built with TypeScript, Bun, and &lt;a href="https://grammy.dev/" rel="noopener noreferrer"&gt;grammY&lt;/a&gt;. The project is &lt;a href="https://github.com/Mark-Life/telegram-claude" rel="noopener noreferrer"&gt;open source&lt;/a&gt; – still early and has some rough edges, but I already use it daily for real work. Voice messages in particular make it feel like you're just talking to your dev environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tips &amp;amp; Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claude Code is slow / OOM&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;4GB RAM is tight if the project is large. Upgrade to CX32 (8GB) if needed&lt;/li&gt;
&lt;li&gt;Close unused Tmux sessions to free memory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;SSH connection drops frequently&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add to your SSH config:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;~/.ssh/config&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ~/.ssh/config
Host vps
  HostName 100.x.x.x
  User agent
  ServerAliveInterval 60
  ServerAliveCountMax 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Caddy not getting SSL cert&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify DNS A record points to VPS public IP&lt;/li&gt;
&lt;li&gt;Check port 443 is open: &lt;code&gt;sudo ufw status&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check Caddy logs: &lt;code&gt;sudo journalctl -u caddy&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tmux session lost scroll history&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increase &lt;code&gt;history-limit&lt;/code&gt; in &lt;code&gt;.tmux.conf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Ctrl+B, [&lt;/code&gt; to enter scroll mode, &lt;code&gt;q&lt;/code&gt; to exit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phone keyboard tips&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terminus supports custom key mappings – map Ctrl, Escape, etc.&lt;/li&gt;
&lt;li&gt;Use Tmux prefix &lt;code&gt;Ctrl+A&lt;/code&gt; (easier to reach than &lt;code&gt;Ctrl+B&lt;/code&gt; on phone keyboards)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Security checklist&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SSH accessible only via Tailscale (never on public IP)&lt;/li&gt;
&lt;li&gt;Keep system updated: &lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tailscale ACLs for extra lockdown if sharing the Tailnet&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much does it cost to run Claude Code on a VPS?
&lt;/h3&gt;

&lt;p&gt;~$7/mo for a Hetzner CX22 VPS + Claude Code subscription ($20/mo for Pro, $100/mo for Max, $200/mo for 10xMax). Tailscale is free for personal use. Total: $27–207/mo depending on your Claude plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run Claude Code on AWS, DigitalOcean, or Linode instead of Hetzner?
&lt;/h3&gt;

&lt;p&gt;Yes. Any Ubuntu VPS with SSH access works. I use Hetzner personally, but DigitalOcean, Linode, and Vultr all work the same way. AWS EC2 works too but is more complex to set up and usually more expensive for equivalent specs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need Tailscale for VPS SSH, or is regular SSH enough?
&lt;/h3&gt;

&lt;p&gt;I strongly recommend Tailscale. Exposing SSH on a public IP – even with key auth – means your server is one unpatched Ubuntu vulnerability away from being compromised. With Tailscale, the SSH port is never exposed to the public internet. There's nothing to scan, nothing to brute-force, no attack surface. You also get stable IPs across networks (home WiFi, mobile data, coffee shop) without port forwarding or dynamic DNS. Tailscale + ufw firewall is the baseline I'd consider safe for a development VPS.&lt;/p&gt;

&lt;h3&gt;
  
  
  What VPS specs do I need for Claude Code? (CPU, RAM, storage)
&lt;/h3&gt;

&lt;p&gt;2 vCPU / 4GB RAM handles one Claude Code instance and a dev server fine for small-to-medium projects. For larger projects or running multiple Claude instances in parallel, go 4 vCPU / 8GB RAM. Disk is rarely the bottleneck – 40GB is plenty unless you're running Docker images.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use VS Code Remote SSH with Claude Code?
&lt;/h3&gt;

&lt;p&gt;Yes, VS Code Remote SSH works over Tailscale. But you lose the main benefit – working from your phone. The terminal-only workflow is intentional: Claude Code does the editing, you just direct it. If you prefer visual editing, VS Code Remote SSH + Cursor is a valid alternative for desktop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is Claude Code &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; safe to use?
&lt;/h3&gt;

&lt;p&gt;I run everything with &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;. The reason it works for me: this is a VPS, not my personal laptop. If Claude somehow destroys the environment, I don't lose anything irreplaceable – only project files (backed by git) and dev-only environment variables. No production credentials, no personal data.&lt;/p&gt;

&lt;p&gt;That said, in this mode Claude can theoretically run &lt;code&gt;rm -rf&lt;/code&gt;, &lt;code&gt;git push --force&lt;/code&gt;, or drop a database without asking. To mitigate this, Claude Code supports &lt;a href="https://docs.anthropic.com/en/docs/claude-code/hooks" rel="noopener noreferrer"&gt;hooks&lt;/a&gt; – shell scripts that intercept commands before execution. You can set up safeguards like: block &lt;code&gt;rm -rf /&lt;/code&gt; patterns, prevent &lt;code&gt;git push --force&lt;/code&gt; to main, or enforce &lt;code&gt;drizzle-kit migrate&lt;/code&gt; instead of &lt;code&gt;drizzle-kit push&lt;/code&gt;. Hooks give you the speed of autonomous mode with guardrails where they matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to set up a shared VPS for multiple developers with Claude Code?
&lt;/h3&gt;

&lt;p&gt;Create separate Linux users, each with their own home directory, tmux sessions, and Claude Code auth. Tailscale ACLs can restrict who can SSH in. Each user's projects stay isolated.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>claude</category>
    </item>
  </channel>
</rss>
