A self-hostable deployment platform. Connect a Git repository, describe the environment you want, and gitrock provisions the infrastructure and deploys into it — onto your own servers over SSH + Docker, or onto AWS.
The interesting problem here is not the deploy button. It is that an environment's real state lives in someone else's cloud, not in your database, and a provisioning run can take ten minutes and fail halfway. So nothing long-running happens inside a request: every provision, deploy, destroy, plan and validate is a durable job with its own status, its own logs and its own failure mode.
Organization
└── Project
├── App repo URL, branch, startup script, port, deployment status
└── Environment provider, security mode, provisioning status
└── EnvironmentResource typed resource + provider config + output variables
An App is a thing you deploy: a repository, a branch, a startup script and a port. An Environment is a place to deploy it: a provider, a security mode, and a set of typed resources (compute, database, storage, cache, queue, load balancer, network, DNS, CDN, monitoring, security). A Job is any operation that changes either of them.
Environment variables are scoped to an app within a project, and credentials never sit in the
application database in plaintext — @gitrock/secrets owns encryption and decryption at the
boundary.
Every environment picks one, and it changes the network topology that gets provisioned:
| Mode | Topology | Trade-off |
|---|---|---|
simple |
Single public subnet, direct internet access | Cheapest; fine for staging and side projects |
secure |
Public/private subnets, NAT gateway, ALB | Real isolation; costs more per month to run |
This is deliberately a two-way choice made at environment creation rather than a pile of independent networking toggles, because the failure mode of the toggles is an environment that is half-isolated and nobody notices.
A Turborepo monorepo, pnpm workspaces.
apps/
web React 19 console — Mantine, Redux Toolkit, TanStack Query, TipTap
api Express control plane — auth, orgs, projects, apps, environments, secrets
worker BullMQ consumers — provision.worker.ts, deploy.worker.ts
packages/
iac Provider factory, workspace management, SSH executor
db Prisma schema and generated client (PostgreSQL)
secrets Encrypted credential storage
types Shared TypeScript types
validations Shared zod schemas — one definition for the API and the console
ui Shared component library
The API and the workers share @gitrock/db, @gitrock/iac and @gitrock/secrets, so a
provisioning run reads exactly the same schema and credentials the console wrote.
- Console — React 19, TypeScript, Vite, Mantine, Redux Toolkit, TanStack Query
- API — Node, Express, Prisma, PostgreSQL, JWT, bcrypt, ssh2
- Queue — BullMQ on Redis
- Infrastructure — Terraform generated per environment by provider adapters behind one factory
interface:
SSHDockerProviderfor self-hosted and bare metal,AWSProviderAdapterfor AWS - Tooling — Turborepo, pnpm, ESLint, Prettier, TypeScript 5.9
pnpm install
# PostgreSQL and Redis must be reachable; see each app's .env
pnpm --filter @gitrock/db prisma migrate deploy
pnpm dev # turbo run dev --parallel: console, API and worker togetherThe console runs on Vite's dev server; the API and the worker are separate processes, because the worker is the thing you want to scale and restart independently of the control plane.
Work in progress, and honest about it. Two provider adapters are implemented — SSH + Docker (the deepest one) and AWS. GCP, Azure and DigitalOcean exist as enum values and nothing more. There is no hosted offering; this is a platform you would run yourself.
What is done is the part that is tedious to retrofit later: the job model, the secrets boundary, the shared validation and type packages, and a provider interface that generates Terraform rather than shelling out to a provider-specific CLI in each worker.

