Self-hosted deployment platform. Link a GitHub repo, push code, get a live URL. Postgres, Redis, and MongoDB available as on-demand managed services. Everything runs as Docker containers on a single machine you control.
GitHub push → webhook → build (Dockerfile or Nixpacks) → rolling deploy → Traefik URL
Six Go microservices, each with its own SQLite database:
| Service | Port | Role |
|---|---|---|
| gateway | 8080 | Public REST + SSE API. Bearer-token auth. |
| control-plane | 50051 | Projects, services, sealed env vars, orchestration. |
| github | 50052 / 8088 | GitHub App tokens, push webhook receiver. |
| builder | 50053 | Clone → Dockerfile/Nixpacks detection → docker build → stream logs. |
| deployer | 50054 | Rolling container deploys, Traefik label routing, deploy history. |
| provisioner | 50055 | Managed Postgres/Redis/Mongo containers on project networks. |
Traefik provides automatic TLS (Let's Encrypt) and routes
<service>.<project>.<BASE_DOMAIN> to each deployed container.
- Docker Engine 24+ with Compose V2 (
docker compose) - A public domain with DNS pointed at your server (for TLS + webhooks)
- A GitHub App — optional, only needed for private repos
git clone https://github.com/LevantateLabs/Prokestro
cd Prokestro
cp .env.example .envEdit .env:
BASE_DOMAIN=example.com # services route as <svc>.<proj>.example.com
ACME_EMAIL=you@example.com # Let's Encrypt registration email
MASTER_KEY= # run `make secret` and paste the output here
PROKESTRO_AUTH_TOKEN=changeme # bearer token for the API; empty = no auth
LOG_LEVEL=infoGenerate a master key:
make secret # prints a fresh 64-char hex key; paste into MASTER_KEY=- GitHub → Settings → Developer settings → GitHub Apps → New GitHub App
- Set:
- Webhook URL:
https://github.<BASE_DOMAIN>/webhooks/github - Permissions: Contents → Read-only
- Subscribe to events: Push
- Webhook URL:
- Generate and download a private key.
- Install the App on your repo.
- Add to
.env:
GITHUB_APP_ID=123456
GITHUB_APP_PRIVATE_KEY="$(cat your-app.2024-01-01.private-key.pem)"
GITHUB_WEBHOOK_SECRET=your-webhook-secretFor public repos leave those three vars empty. Prokestro will clone without a token.
make up # docker compose up --build -d
make logs # tail all service logsVerify:
docker compose ps # 7 containers, all "Up"
curl http://localhost:8080/healthz
# {"status":"ok"}The gateway is also routed through Traefik at https://api.<BASE_DOMAIN> once DNS resolves.
TOKEN=changeme
API=http://localhost:8080
curl -X POST $API/v1/projects \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "myapp"}'{"id":"proj_a1b2c3d4e5f6","name":"myapp","network":"prokestro_proj_a1b2c3d4e5f6",...}curl -X POST $API/v1/projects/<project_id>/services \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "web",
"repo_full_name": "yourorg/yourrepo",
"branch": "main",
"container_port": 3000,
"github_installation_id": 12345678
}'github_installation_id is in the GitHub App installation URL
(/settings/installations/<id>). Use 0 for public repos.
curl -X POST $API/v1/services/<service_id>/env \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key": "NODE_ENV", "value": "production"}'Automatic: push to the configured branch — the webhook fires and triggers a build.
Manual:
curl -X POST $API/v1/services/<service_id>/build \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
# {"build_id":"bld_...","url":"https://web.myapp.example.com"}curl -N "$API/v1/services/<service_id>/logs" \
-H "Authorization: Bearer $TOKEN"Events emitted: log (one build/deploy line), status (PENDING → RUNNING → SUCCEEDED/FAILED), url (final Traefik URL).
Add a Postgres database to a project:
curl -X POST $API/v1/projects/<project_id>/managed-services \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "db", "kind": "postgres"}'Supported kinds: postgres, redis, mongo.
Prokestro automatically injects the connection env vars (DATABASE_URL, POSTGRES_HOST, REDIS_URL, etc.) into every git-backed service in the project. The next deploy picks them up with no manual configuration.
The managed container is only reachable inside the project's private network via its alias (e.g. postgres:5432). It is never published to the host.
| Method | Path | Description |
|---|---|---|
| GET | /healthz |
Health check (no auth) |
| POST | /v1/projects |
Create project |
| GET | /v1/projects |
List projects |
| GET | /v1/projects/{id} |
Get project |
| POST | /v1/projects/{id}/services |
Create git-backed service |
| GET | /v1/projects/{id}/services |
List services in project |
| POST | /v1/projects/{id}/managed-services |
Provision managed datastore |
| GET | /v1/services/{id} |
Get service |
| GET | /v1/services/{id}/env |
List env var keys (values never returned) |
| POST | /v1/services/{id}/env |
Set / update an env var |
| POST | /v1/services/{id}/build |
Trigger build + deploy |
| POST | /v1/services/{id}/rollback |
Re-deploy the previous succeeded image |
| GET | /v1/services/{id}/builds |
Build history (newest first) |
| GET | /v1/services/{id}/deployments |
Deployment history |
| GET | /v1/services/{id}/logs |
SSE live log stream (triggers build) |
| GET | /v1/builds/{id}/logs |
Historical build log lines |
Pass cpu_shares and memory_mb in CreateService:
{
"name": "web",
"repo_full_name": "yourorg/yourrepo",
"branch": "main",
"container_port": 3000,
"cpu_shares": 512,
"memory_mb": 256
}cpu_shares is Docker's relative CPU weight (1024 ≈ 1 core). memory_mb is a hard limit enforced at the cgroup layer.
make up # build images and start all services
make down # stop and remove containers
make logs # tail all service logs
make secret # generate a fresh MASTER_KEY value
make test # unit + integration tests
make build # compile all binaries into ./bin/
make gen # regenerate Go from proto/ (requires buf)Run individual services against local ports:
export MASTER_KEY=$(make -s secret) \
PROKESTRO_DATA_DIR=./data \
BASE_DOMAIN=localhost
go run ./cmd/control-planeServices dial each other using the defaults in internal/platform/config/config.go
(CONTROLPLANE_ADDR, BUILDER_ADDR, etc.). Override to taste.
Single-host, shared daemon. Builder, deployer, and provisioner mount
/var/run/docker.sock and run as root. Containers they create are siblings on
the host daemon — no image registry is required.
Socket access is root-equivalent on the host. This is the accepted trade-off for single-host self-hosting. Do not expose the Docker socket to untrusted workloads.
Per-project networks. Each project gets a dedicated Docker bridge network
(prokestro_proj_<id>). Managed datastores are unreachable from outside the
network; app containers connect by DNS alias (e.g. postgres, redis).
Sealed secrets. Env vars are encrypted with AES-256-GCM at rest using
MASTER_KEY. Plaintext is held in memory only during TriggerBuild, passed
in-process to the deployer, and never logged or persisted as plaintext.
Build detection. The builder looks for a Dockerfile or Dockerfile.* first.
If none is found, it falls back to the Nixpacks CLI — the same auto-detection
Railway uses for zero-config builds.
Rolling deploys. Deployer starts the new container, polls ContainerInspect
until State.Running is true (up to 60s), then removes the old container. If the
new container exits immediately, the old container is left running and the deploy
is marked failed.
Rollback. POST /v1/services/{id}/rollback queries deploy history, skips the
current running version, and re-deploys the previous succeeded image — no clone or
rebuild.