# I almost replaced Lovable with a $5 VPS, Dokploy and one MCP gateway

> Source: <https://dev.to/k2sodev/i-almost-replaced-lovable-with-a-5-vps-dokploy-and-one-mcp-gateway-3mn9>
> Published: 2026-09-18 13:07:06+00:00

Lovable is nice. You describe a site, an agent writes it, you click publish. It also costs $25–50 a month for the tiers that are actually usable, the code lives on their side, and the agent only knows how to do one thing.

I already pay for Claude. I already have a VPS with Dokploy on it. The missing piece was a way for the agent to deploy, and gateways for that already exist — MetaMCP, IBM’s ContextForge, a few others. None of them gave me the combination I wanted: multiple runtimes, one container, an OAuth server of its own, and a UI I don’t mind looking at. So I wrote one.

`@dokploy/mcp` server, running inside Junctio as a stdio process
The client talks to one URL. Junctio runs the Dokploy MCP server, keeps the Dokploy API key, and exposes the tools over Streamable HTTP with OAuth.

``` php
# Schema
AI Agent App --OAuth--> Junctio --stdio--> @dokploy/mcp --> Dokploy API
                        (API key lives here)
```

The prompt is something like: “Build a landing page for X, push it to my GitHub, deploy it on Dokploy under x.example.com”. The agent writes the page, creates the app in Dokploy, points it at the repo, adds the domain and triggers the build. A few minutes end to end, most of it the Docker build.

Every tool call goes through the gateway and lands in the request log: method, tool, upstream, status, timing. Bodies are not stored: arguments and results can carry anything your agent touched, and a log full of that is a leak waiting to happen.

`npx @dokploy/mcp` in the client
claude.ai has no stdio. Claude Desktop and Claude Code will happily spawn a local stdio server; claude.ai in the browser and ChatGPT connectors will not, and they can’t send an API key header either. A custom connector means a remote MCP server with a real OAuth authorization server behind it: RFC 8414 metadata, dynamic client registration, PKCE, a consent screen. Junctio has that built in. Add the connector URL, approve at the consent screen with the admin password, done. No Cloudflare Worker wrapper, no separate auth service.

~600 tools is too many. The official Dokploy MCP server exposes the whole API, upwards of 600 tools at the moment.. That’s a lot of context, and the agent picks the wrong application-* call half the time. In Junctio I put the server in a namespace and hid everything except about 20: create project, create application, set source, set domain, deploy, read logs. Plus a short instruction block on the namespace (“never delete, ask before creating databases”). The client only sees the small set.

The key stays on the server. With the stdio setup every client machine has the Dokploy API key in a mcp.json. With the gateway there is one key, in one place, encrypted at rest. Each client gets its own OAuth client or API key and I can revoke it from the UI.

```
services:
  junctio:
    image: ghcr.io/k2so-dev/junctio:latest
    pull_policy: always
    restart: unless-stopped
    environment:
      JUNCTIO_SECRET: ${JUNCTIO_SECRET:?set JUNCTIO_SECRET, e.g. openssl rand -hex 32}
      JUNCTIO_BASE_URL: ${JUNCTIO_BASE_URL:?set JUNCTIO_BASE_URL, e.g. https://mcp.example.com}
      JUNCTIO_TRUST_PROXY: "true"
      JUNCTIO_ADMIN_TOKEN: ${JUNCTIO_ADMIN_TOKEN:-}
      LOG_LEVEL: ${LOG_LEVEL:-info}
    volumes:
      - junctio-data:/data
      - junctio-cache:/cache
    tmpfs:
      - /tmp
    read_only: true
    networks:
      - dokploy-network

networks:
  dokploy-network:
    external: true

volumes:
  junctio-data:
  junctio-cache:
```

Add it in Dokploy as a Compose service with the Raw provider. No published ports: Dokploy routes through its own Traefik, so the service joins dokploy-network and gets its domain on the Domains tab, pointed at container port 3000 with Let’s Encrypt on. junctio-cache holds the npx and uvx package caches — throwaway, but without it every cold start is slow. Outside Dokploy, put Caddy in front instead; either way OAuth needs TLS, while an API-key endpoint works fine over plain http on localhost.

Then in the UI: add server > runtime npx (or bunx) > package `@dokploy/mcp` > env `DOKPLOY_URL`, `DOKPLOY_API_KEY`. Create a namespace, hide tools, create an endpoint with OAuth on. Paste the URL into your agent app as a custom connector.

Full walkthrough: [https://junctio.pages.dev/use-cases/claude-ai-connector/](https://junctio.pages.dev/use-cases/claude-ai-connector/)

It is not Lovable. No live preview, no visual editor, no “click this button and change its color”. You get a chat and a deploy. If you want to iterate on UI visually, this is worse.

It is also not safer by default. You are giving an agent write access to your deployment platform. I hid the destructive tools and the agent still once tried to redeploy the wrong app because two had similar names. Keep a namespace per project, keep the tool set small, read the logs. Junctio can also run a scheduled vulnerability check on the servers it launches through npx, bunx, node or uvx, which matters when you install packages from npm with one click. It’s off by default, it’s advisory data rather than analysis, and container images, custom commands and remote servers are marked “not audited” because the gateway can’t tell what’s inside them:

VPS $5 a month. Dokploy free. Junctio free, MIT. AI subscription I already had.

Memory: Junctio idles at about 60 MB. A running stdio server adds its own footprint, `@dokploy/mcp` is another 60 MB or so. Idle servers get stopped after a timeout and restarted on the next call, so most of the time you’re back at 60 MB. It’s TypeScript on Bun, not Go, and I’m fine with that. I can maintain it.

The same trick works with anything that has an OpenAPI spec: run an OpenAPI-to-MCP server inside the gateway, hide the tools you don’t want, write instructions for the rest, and the agent can manage your shop, your DNS, your VPN panel. The gateway also browses the public MCP registry, so adding the next server is a click:

Repo: [https://github.com/k2so-dev/junctio](https://github.com/k2so-dev/junctio)

Docs: [https://junctio.pages.dev/docs/](https://junctio.pages.dev/docs/)
