open source/MIT licensed/self-hostable

The private PaaS
you self-host.

Any server, any cloud, connected over WireGuard. Only the gateway faces the internet.

$ sudo bash -c "$(curl -fsSL meshploy.com/install.sh)"
Route to anything on the mesh, deploy or not Monitor nodes and services Scale horizontally across any cloud
How a request flows

One public node.
Everything else is dark.

Client
HTTPS
Browser initiates HTTPS to api.acme.com. DNS resolves to the gateway, the only machine in the cluster with a public IP.
Caddy
TLS · :443
Caddy terminates TLS at :443. Wildcard cert issued via DNS-01 challenge against CoreDNS. No cert-manager, no Ingress CRDs.
Proxy
:8081 · ask&resolve
Reads the Host header, queries Postgres for the matching route, resolves the target mesh IP and port. Cache refreshes every 30s.
Public internet
Browser initiates HTTPS to api.acme.com. DNS resolves to the gateway — the only machine in the cluster with a public IP.
Edge gateway · Caddy
Caddy terminates TLS at :443. Wildcard cert issued via DNS-01 challenge against CoreDNS. No cert-manager, no Ingress CRDs.
Edge gateway · Proxy
Proxy reads the Host header, queries Postgres for the matching route, resolves the target mesh IP and port. Cache refreshes every 30s.
WireGuard mesh
Request enters the Headscale overlay. Workers have zero public ports. All inter-node traffic is encrypted end-to-end.
WireGuard mesh · Pod
K3s pod at 100.64.0.x:3000 receives the proxied request and responds back through the same encrypted path to the gateway.
K3s pod
100.64.0.x:3000
Receives the proxied request and responds back through the same encrypted path to the gateway.
WireGuard
encrypted overlay
Request enters the Headscale overlay. Workers have zero public ports. All inter-node traffic is encrypted end-to-end.
Why it's different

Five deliberate departures
from the way things are done.

Each of these choices replaces a common dependency with something smaller, more direct, and debuggable by one person at 2am.

Meshploy is a routing layer first. Deployments are built on top of it, not the other way around.

Routing

A routes table, not an Ingress controller.

Routes live in Postgres. The proxy caches in memory and refreshes every 30s. Adding a route is a single API call, no deploy step needed.

hostname
target_ip
port
api.acme.com100.64.0.23000
app.acme.com100.64.0.38080
admin.internal.acme.com100.64.0.48081
Nginx · Traefik · Ingress CRDs → Postgres + 200-line Go proxy
Secrets

AES-encrypted columns, not k8s Secrets.

Secrets are stored as encrypted columns in the database. Application code never handles encryption directly. No external key management service needed.

DATABASE_URLenc:AES256-GCM STRIPE_KEYenc:AES256-GCM
Builds

K8s Jobs, not external CI.

Build pipelines run as ephemeral K8s Jobs on nodes labelled role=builder. Logs stream back over the same API. No GitHub Actions runners to maintain.

TLS

Caddy handles TLS. Your DNS stays yours.

Caddy renews certificates automatically. A built-in DNS server handles the domain verification challenge. No extra operators or config files to manage.

Let's Encrypt issues challenge
Caddy writes TXT to CoreDNS zone
Cert issued *.user.acme.com
Network

WireGuard mesh, not VPC lock-in.

Every node (Hetzner, AWS, your home laptop) joins the Headscale-managed mesh. Stable IPs in 100.64.0.0/10. Cross-cloud by default.

Authentication

Authorization is explicit, not hidden.

Every protected endpoint calls requireUser() directly. Access control is visible at the point where it matters, not tucked away in a router group you have to go looking for.

// explicit auth at every protected call site
func (h *Handler) GetNode(ctx, input) (...) {
  if _, err := requireUser(ctx); err != nil {
    return nil, err
  }
  return h.svc.GetNode(ctx, input.ID)
}
Install

Three commands.
One control plane.

Point a subdomain at your server, run one script, and you have a working PaaS in about five minutes. Workers join by pasting one command.

step 01

Point your domain

Add an NS record at your registrar pointing a subdomain at your server's IP. Meshploy runs its own DNS server so no third-party plugin is needed. DNS takes a few minutes to propagate.

# at your registrar
meshploy.acme.com  NS  65.108.42.18
step 02

Install the gateway

Run as root on any internet-facing Linux server. Installs Docker if needed, brings up Caddy, Headscale, CoreDNS, and the API. The script verifies DNS before completing.

# on the public node
sudo bash -c "$(curl -fsSL \
  meshploy.com/install.sh)"
step 03

Join workers

Same script, worker flag. The node self-registers with a provisioning token, joins Headscale, and is ready to schedule workloads.

# on each worker
sudo meshploy node install \
  --token mprov-8a3f...b712
Compare

Three positions
against the alternatives.

Meshploy isn't trying to be everything. It's trying to be the right shape for the gap between "deploy to one server manually" and "hire a platform team."

vs. Vercel · Railway · Render

cloud paas
You own the infrastructure. No egress fees, no provider lock-in, no surprise bills.
Same dev experience. Git push, build, deploy, route. Logs streaming, instant rollback.
Multi-cloud, by default. One node in Hetzner, one in AWS, one at home. Same mesh.

vs. Coolify · Dokploy · Uncloud

self-hosted paas
Workers are dark. No public ports on worker nodes. Compromise of one worker doesn't expose any service.
Encrypted secrets at rest. AES-256-GCM at the application layer. App code never touches crypto.
Granular ownership. Orgs, projects, and per-resource permissions, built into the core data model.

vs. raw Kubernetes

diy on k8s
No Ingress controller, no cert-manager. Caddy plus a 200-line Go proxy handles both jobs.
No external CI runners. Builds run as K8s Jobs on dedicated builder nodes you label.
Operable by one person. If you can read 20 files of Go, you understand the entire control plane.
CLI

Everything is one binary,
at /usr/local/bin/meshploy.

A static Go binary installed by the setup script. It wraps the REST API and shells out to install.sh / uninstall.sh for node-level operations. Authenticate once, then manage everything from anywhere.

~ · meshploy@gateway-fsn1
$ meshploy auth login --api-url https://app.acme.com
Authenticated as [email protected] · token cached to ~/.meshploy/config
 
$ meshploy node list
ID NAME ROLE MESH IP STATUS
────── ───────────── ────── ─────────── ──────
01jf3m gateway-fsn1 server 100.64.0.1 online
02jf3m worker-ash1 agent 100.64.0.2 online
03jf3m worker-sin1 agent 100.64.0.3 online
04jf3m builder-home agent 100.64.0.4 online
05jf3m worker-hel1 agent 100.64.0.5 offline · last seen 42m ago
 
$ meshploy deploy --service api --ref main
→ triggering build on builder-home …
Built ghcr.io/acme/api:a4f21e8 (94s)
Pushed to registry (12.4 MB)
Rolling update: 2 of 2 replicas healthy
Route api.acme.com100.64.0.2:3000
done in 1m 47s
Stack

Boring tools,
composed deliberately.

None of these were built for Meshploy. The question was: which mature, well-understood tools assemble into a coherent stack one person can operate and understand completely.

Control plane
Go 1.22+
Chi + Huma OpenAPI 3.1
PostgreSQL 15+
GORM
Data plane
K3s v1.30
Caddy v2
CoreDNS
Headscale
Frontend
Vite 6
React 19
TanStack Router
Tailwind v4
Builds
Nixpacks
Heroku Buildpacks
Dockerfile
Pre-built image

Self-host in 5 minutes.
Scale across clouds in 5 hours.

One curl on a fresh box. One NS record at your registrar. That's it for the gateway. Workers join by pasting one command.