liftwork
liftwork
Plug-and-play Kubernetes build & deploy platform. A self-hosted internal Heroku — drop in a Helm chart, point at a repo, get a deploy.
README · Project overview
today 10 steps, 3 tools,
half a day of yak-shaving.
with liftwork git push.
~/app on main
$ git push origin main
→ liftwork: build queued (job 4f2a1c)
→ buildkit: pushed sha256:9c8e…@registry
→ deploy: rollout ready in 41s
liftwork
Problem
02
The Kubernetes onboarding tax
Getting any repo onto Kubernetes today is a 10-step ordeal.
01
Write a Dockerfile
02
Build the image
03
Push to a registry
04
Write manifests or a Helm chart
05
Set up RBAC
06
Manage secrets
07
kubectl apply
08
Watch the rollout
09
Wire up logs & metrics
10
Repeat for every service
ArgoCD & Flux assume you've already done all of that. · Heroku, Render, Railway lock you off your own cluster.
02 · Problem
liftwork
Solution
03

Install once.
git push becomes a deploy.

One Helm chart in your cluster. Connect a repo through the dashboard or a liftwork.yaml at the root. From then on, your push is your deploy — on infrastructure you own.
03 · The promise
liftwork
How it's built
04
Architecture
One Helm chart, six moving parts.

Boringly conventional
where it counts.

FastAPI in front. Postgres for state. Redis-backed workers for the heavy lifting. BuildKit rootless inside the cluster — no privileged containers, no external build farm.

UIReact + shadcn dashboard
APIFastAPI · webhooks · OpenAPI
QUEUERedis + arq (asyncio-native)
BUILDBuildKit rootless · in-cluster Job
DEPLOYk8s python client · server-side apply
OBSOpenTelemetry + Prometheus
Surface
React + shadcn UIapps/dashboard
FastAPIapps/api
PostgresSQLAlchemy 2.0 async
JOBS
Engine
Redisarq queue
Worker(s)apps/worker
Build engineBuildKit · in-cluster Job
Deploy enginek8s python client · SSA
OTel + PrometheusOTLP-exportable
04 · Architecture
liftwork
git push → ready
05
The deploy pipeline
Seven steps from git push to a healthy rollout.
git push
Webhook fires; arq enqueues run_build
apps/api
01
Worker shallow-clones the branch into a tempdir
git clone --depth 1
02
Orchestrator detects language, renders Dockerfile.liftwork
unless repo
committed its own
03
BuildKit Job runs in-cluster; logs streamed back
moby/buildkit:rootless
batch/v1 Job
04
Image pushed to in-cluster registry; status → succeeded
parses
LIFTWORK_DIGEST=…
05
If auto_deploy, run_deploy is enqueued
configurable
per-app
06 → ready
Server-side apply Deployment + Service (+ Ingress); watch rollout
apps_v1.read_
namespaced_deployment
Mock-mode default for local dev · LIFTWORK_WORKER__EXECUTOR=kind flips on the real BuildKit-in-pod path
05 · Deploy pipeline
liftwork
v1 stack
06
Stack choices
Boring, mature, OTel-first.
Concern
Choice
Why
Web framework
FastAPI + uvicorn
async, OpenAPI generated, OTel-first
ORM
SQLAlchemy 2.0 async + Alembic
industry standard, mature async
Queue
Redis + arq
asyncio-native, light; abstracted for v2 swap to Temporal
Build executor
BuildKit rootless · in-cluster Job
no privileged containers; cache + multi-arch + secret mounts
Registry
GHCR (ECR / Quay / Hub stubbed)
repo-native; works out of the box
Auth (v1)
local password + JWT
OIDC arrives in v2
Telemetry
structlog + OpenTelemetry + Prometheus
OTLP-exportable to Datadog, HyperDX, Grafana
06 · Stack choices
liftwork
Local dev
07
Quick start
Five minutes to a running API on your laptop.

WSL2 / Ubuntu
or any POSIX shell.

Mock-mode worker walks every state transition without touching a registry or a cluster — perfect for iterating on the API and dashboard. The kind path in the README turns on the real BuildKit-in-pod build when you're ready.

runtimePython 3.12 · Node 20+ · pnpm · uv
infraDocker · kubectl · Helm
apilocalhost:7878/docs
# clone & bootstrap $ git clone https://github.com/P0intMaN/liftwork.git $ cd liftwork && make bootstrap → uv sync · pnpm install · pre-commit install
# bring up postgres + redis $ cp .env.example .env $ make dev-up
# two shells, side by side $ make dev-api # http://localhost:7878/docs $ make dev-worker # mock mode — no cluster needed
07 · Quick start
liftwork
Codebase
08
What's in the repo
A tidy monorepo. pnpm + uv workspaces.
liftwork/ ├── apps/ │ ├── api/ # FastAPI · REST · webhooks │ ├── worker/ # arq build + deploy runner │ └── dashboard/ # Vite + React + shadcn ├── packages/ │ └── core/ # settings · db · telemetry · models ├── charts/liftwork/ # Helm chart + Grafana dashboards ├── deploy/ # docker-compose · kind cluster ├── docs/ # install · liftwork.yaml · obs ├── site/ # GitHub Pages landing + deck └── .github/ # CI · release · e2e workflows
apps/api
FastAPI service. REST endpoints, GitHub webhooks, dashboard backend, OTel-instrumented end-to-end.
apps/worker
arq worker. Runs build + deploy jobs. Mock + kind executors today; the interface is what slots Temporal in for v2.
packages/core
Settings, async SQLAlchemy database layer, telemetry plumbing, and the SQL models — shared across api and worker.
charts/liftwork
One Helm chart. Optional bundled Postgres & Redis subcharts so you can install with zero external dependencies.
08 · Repo layout
liftwork
Self-diagnosing
09
Diagnoses its own failures
From 600s opaque timeout to ~12s actionable error.

Polls Pod statuses + namespace Events; classifies; bails fast.

Terminal signals (image-pull, crash-loop, config error) bail immediately. Soft signals (probe failures, scheduling) require three consecutive observations — slow-start apps still get a chance.

Each diagnosis bumps a labelled Prom counter so you can alert on each category. Build errors classify too — registry_auth, dockerfile_syntax, network.

~12s
median fail-fast for the most common breakage modes — vs. 600s before.
image_pull_failed
ImagePullBackOff / ErrImagePull
"image <ref> couldn't be pulled. Check image_repository and that the registry is reachable."
crash_loop
CrashLoopBackOff · exitCode
"container is crash-looping (exit code 137). Check the container logs — your app exits on start."
port_mismatch
probe: connection refused
"container is up but isn't listening on port 9999. Set deploy.port in liftwork.yaml."
health_path_404
probe: HTTP 404
"container responds on :3000 but /healthz returned 404. Set deploy.health_check.path."
config_error
CreateContainerConfigError
"container failed to start: secret 'my-db' not found in namespace."
registry_auth
denied: requested access denied
"registry rejected the image push: authentication failed. Configure push credentials."
09 · Diagnostics
liftwork
OTel-first
10
Observable by default
Logs · metrics · traces wired through the whole product.

Three pillars, BYO collector.

Default install emits everything; bring your own collectors. --set serviceMonitor.enabled=true drops it straight into kube-prometheus-stack.

Logs
JSON structlog to stdout — one line per event with request_id, build_id, deploy_id propagated.
Metrics
~18 Prometheus instruments with bounded labels — build/deploy duration, k8s API latency, queue depth, error categories.
Traces
OTel root spans on every build & deploy job. OTLP-exportable to Tempo, Datadog, HyperDX, Honeycomb.
Boards
Two Grafana JSONs at charts/liftwork/dashboards — Operations + SRE.
/metrics — selected
  • liftwork_builds_finished_total{language,status}
  • liftwork_build_duration_seconds_bucket{language,status,le}
  • liftwork_build_image_bytes_bucket{language,le}
  • liftwork_deploys_finished_total{cluster,outcome}
  • liftwork_deploy_duration_seconds_bucket{cluster,outcome,le}
  • liftwork_active_builds
  • liftwork_active_deploys
  • liftwork_k8s_api_latency_seconds_bucket{verb,resource,le}
  • liftwork_k8s_api_errors_total{verb,resource,status}
  • liftwork_cluster_healthy{cluster}
  • liftwork_queue_depth{job}
  • liftwork_webhooks_received_total{event,action}
  • liftwork_errors_total{category,stage}
10 · Observability
liftwork
Status
11
Roadmap
Pipeline runs end-to-end. Polish, then v2.
v1 · live

Today

  • Build & deploy pipeline e2e against kind
  • BuildKit rootless · in-cluster Job
  • Server-side apply · rollout watch
  • liftwork.yaml file-wins config
  • Language-aware defaults · rollout diagnostics
  • OTel + Prometheus + Grafana dashboards
  • Helm chart · CI/CD · container images

Now polishing

  • GitHub App install flow
  • Argo-style file/form drift UX
  • Bundled liftwork-observability subchart
  • Worker resumability (drop the single-replica cap)
v2 · soon

After that

  • OIDC auth (Google, GitHub, Okta)
  • ECR · Quay · Docker Hub registry backends
  • Temporal queue (interface stubbed)
  • Preview environments per PR
11 · Roadmap
Try it

One chart in.
Every push out.

Apache 2.0. Self-hosted. No cloud lock-in. Install once into your cluster — the rest is just git push.