Self-hosted · GitOps · Plain Docker

Your own PaaS.
Git is the control plane.

MajNet ships apps to your own hardware the way a platform should feel — every deploy is a commit, every environment is declarative, and there’s no Kubernetes to babysit.

Open source · One binary bootstraps a node · No control-plane SaaS.

Everything a platform should be. Nothing it shouldn’t.

A small, legible control plane you can actually read — two Rust services, plain Docker, and git as the source of truth.

GitOps by default

Every state change is a commit or pull request. `git log` is your deploy history — no imperative drift, no clicking around a UI to change prod.

Plain Docker, no scheduler

Static placement by trust zone — each workload has exactly one home node. No Swarm, no Kubernetes, no quorum. One reconciler drives each node directly.

A project is a GitHub org

Repos, teams, members, webhooks, and app scaffolding are fully bot-managed and declarative. Declare an app; the platform materializes the rest.

Environment classes

production (public, review-gated), stable / testing (VPN, auto-deploy), and ephemeral per-PR previews with a TTL. Placement follows the class.

Secrets & managed databases

SOPS + age secrets that rendering never decrypts, plus managed Postgres, MariaDB, Valkey and MongoDB with per-project logical DBs and backups.

Credential isolation

The GitHub/Tailscale liaison and the node/secrets orchestrator hold strictly separate credentials — neither can act in the other’s domain.

A look at MajNet

Manage projects from a dashboard on your VPN — but every deploy is still a reviewed pull request.

dash.majksa.net
acme · Apps
webghcr.io/acme/web@sha256:9f3c…
production● in sync
apighcr.io/acme/api@sha256:1b7e…
stable● deployed
workerghcr.io/acme/worker@sha256:c4a0…
testing● pending
docs-pr-88preview · ttl 72h
ephemeral● deployed
Dashboard — projects, apps, environment classes and live deploy status, reachable only over your VPN.
dash.majksa.net/projects/majnet/deploys
Projects / majnet / Deployments

Deployments

pending render PRs on majnet/ops
production#4 · render: production
website.yaml +3 −1 modified
name: website
- image: ghcr.io/majnet/website@sha256:fd49cb89…
+ image: ghcr.io/majnet/website@sha256:301bb21a…
ingress:
host: net.majksa.com
port: 80
+ database:
+ engine: postgres
The deploy gate, in the MajNet dashboard — a production change lands as a render PR with its diff. Nothing ships until you approve.

Declare it. Merge it. It’s live.

The whole deploy loop is a pull request. Your review is the gate to production.

01

Declare

Add an app to your project’s ops repo — a name, a template, an image. Commit it. That commit is the intent.

02

Render

The bot renders your manifests into per-environment branches and opens a pull request. Production waits for your review.

03

Deploy

Merge the PR. The reconciler converges the node’s Docker state — blue-green, health-checked, digest-pinned.

Two services. Three nodes. No magic.

A GitHub bot and a reconciler — with strictly separate credentials — drive plain Docker across trust-zoned nodes over a WireGuard mesh.

GitHubplatform org · project orgs (ops + app repos)install · webhooks · render commitsmain node — control planeBotGitHub · TailscaleReconcilerDocker · secretsDashboardVPN-onlyDocker API · WireGuard · mTLSprod node — publicedge (Traefik) routerproduction appsproduction databasesprivate node — internalstable + ephemeral appsper-project VPN ingressdev databasesWireGuard

One source of truth

Your repo is the whole story.

A root platform org holds global config; each project’s opsrepo holds its apps and SOPS-encrypted secrets. There’s no hidden state to drift from — reconstruct any node from git and a restore.

  • Every change is a commit or PR — auditable, revertable.
  • Images pinned by digest, never by tag.
  • Rendering never decrypts secrets; the reconciler decrypts only at deploy, into tmpfs.
  • Archive, never delete — teardown only when config leaves git.
apps/web/production.yaml
name: web
image: ghcr.io/acme/web@sha256:9f3c…
ingress:
  host: app.example.com
  port: 8080
database:
  engine: postgres

Where MajNet fits

The reach of a PaaS and the control of your own box — without a cluster to operate.

MajNetKubernetesManaged PaaSDocker Compose
Runs on your own hardwareAny Debian boxSelf-managedTheir cloudAny box
Git is the source of truthEvery deploy is a PRBring your own GitOpsDashboard / CLIFiles, no workflow
Operational complexityTwo small servicesHighHidden from youLow
Per-PR preview environmentsEphemeral, TTLDIY toolingSome tiersNone
Secrets never in env / plaintextage + tmpfsBase64 by defaultProvider-held.env files
Managed databases + backupsPG/MariaDB/Valkey/MongoOperatorsAdd-ons ($$)Manual
Monthly costYour VPS billCluster overheadScales with usageYour VPS bill

Ship to your own hardware.

Bootstrap a node, install the GitHub App, add one line to the registry. Everything after is automated.