Plan: package LightAPI as a Helm chart (zero-CRUD-code deploys)
Goal
A team that needs a CRUD API over an existing database writes only a
values.yaml (DB connection + the endpoint/table definitions) and runs
helm install. No Python, no CRUD code, no per-project image build.
Why this is small
LightAPI already does the hard part. LightApi.from_config(path) builds a full
CRUD API from a declarative YAML file, supports ${VAR} env substitution in the
DB URL, and can reflect existing tables. So the chart never generates CRUD:
it only has to (1) hand LightAPI that YAML, (2) inject secrets, (3) run the
process, (4) expose it. The YAML the framework already parses is the chart's
config surface.
What is actually missing (three gaps)
Confirmed by reading the repo: no server entrypoint that reads a config path from env, no health route, no Dockerfile/chart.
1. A first-class serve entrypoint (belongs in the package, not the chart)
Add a lightapi serve console command (SOLID: one object owns "boot from
environment"). It replaces the ad-hoc run_server.py.
- New:
lightapi/server.pywith aServerCommandclass. It readsLIGHTAPI_CONFIG(config path),LIGHTAPI_HOST,LIGHTAPI_PORTand callsLightApi.from_config(path).run(host, port). Env parsing is the object's one responsibility; it delegates building tofrom_configand serving torun. lightapi/__main__.py:ServerCommand.from_env().execute()sopython -m lightapiworks too.pyproject.toml:[project.scripts] lightapi = "lightapi.server:main".main()is a thin adapter overServerCommand(no logic in the free function; it constructs the object and calls it).
2. A /healthz route in the framework (belongs in LightApi)
The probe must not depend on a business endpoint. Add /healthz in
LightApi.build_app() / run() where the Starlette route list is assembled
(same spot the auth routes are inserted, lightapi.py:376). A tiny
HealthCheck handler returns {"status": "ok"} (200). Optionally it pings the
DB engine; start with process-only (liveness) and add a DB ping variant for
readiness later.
3. Container image + chart (the packaging itself)
Single generic published image; every deploy reuses it and only swaps
values.yaml.
Deliverables
Dockerfile # generic image: installs lightapi, ENTRYPOINT ["lightapi","serve"]
lightapi/server.py # ServerCommand (env -> from_config -> run)
lightapi/__main__.py # python -m lightapi
charts/lightapi/
Chart.yaml # appVersion pinned to the image tag
values.yaml # documented defaults
templates/
_helpers.tpl # names/labels
configmap.yaml # renders .Values.config (the lightapi.yaml) verbatim
secret.yaml # DATABASE_URL, LIGHTAPI_JWT_SECRET (+ existingSecret opt-out)
deployment.yaml # mounts config, env from secret, /healthz probes
service.yaml
ingress.yaml # optional (.Values.ingress.enabled)
hpa.yaml # optional
serviceaccount.yaml
NOTES.txt # how to reach the API after install
values.yaml shape (the entire developer-facing surface)
image:
repository: iklobato/lightapi
tag: "" # defaults to Chart.appVersion
replicaCount: 2
# Connection to a database that already exists (chart does NOT manage the DB).
database:
# either a full URL...
urlSecret:
existingSecret: "" # name of a Secret holding DATABASE_URL
key: DATABASE_URL
# ...or let the chart create the Secret from this value (dev only):
url: "" # e.g. postgresql://user:pass@host:5432/app
jwt:
existingSecret: ""
secret: "" # LIGHTAPI_JWT_SECRET (required only if endpoints use JWT)
redis:
url: "" # LIGHTAPI_REDIS_URL, only if an endpoint sets cache
# This block is copied verbatim into a ConfigMap and passed to from_config().
# It is exactly the declarative lightapi.yaml the framework already parses.
config:
database:
url: "${DATABASE_URL}" # resolved from the Secret at runtime
defaults:
pagination: { style: page_number, page_size: 20 }
endpoints:
- route: /products
fields:
name: { type: str, max_length: 200 }
price: { type: float }
in_stock: { type: bool, default: true }
meta:
methods: [GET, POST, PUT, DELETE]
filtering: { fields: [in_stock], ordering: [price] }
# To wrap an existing table instead of declaring fields:
- route: /legacy_users
reflect: true
meta: { methods: [GET] }
ingress:
enabled: false
className: ""
hosts: []
autoscaling:
enabled: false
resources: {}
How the pieces wire at runtime
configmap.yamlwrites.Values.configto/etc/lightapi/lightapi.yaml.deployment.yamlsetsLIGHTAPI_CONFIG=/etc/lightapi/lightapi.yamland pullsDATABASE_URL/LIGHTAPI_JWT_SECRET/LIGHTAPI_REDIS_URLfrom the Secret.ENTRYPOINT lightapi serve->ServerCommand.from_env()->from_config()parses the mounted YAML,${DATABASE_URL}is substituted from env, tables are created/reflected, uvicorn serves onLIGHTAPI_PORT(8000).- Liveness/readiness probes hit
/healthz.
Developer workflow (the payoff)
helm install shop charts/lightapi \
-f my-values.yaml # my-values.yaml = DB url + the endpoints block
config.endpoints and
helm upgrade.
Decisions locked
- Database: external (chart is stateless; no bundled Postgres).
- Health: real
/healthzadded to the framework. - Image: one generic published image, config injected via values.
Out of scope (add when needed)
- Bundled Postgres/Redis subcharts (dev-only convenience).
- DB migrations beyond
create_all(LightAPI creates missing tables; it does not run schema migrations). - Multi-tenant / per-endpoint image splitting.
Build order
ServerCommand+__main__+[project.scripts](self-check:python -m lightapiboots a demo config)./healthzroute + one test.- Dockerfile; build and run the image locally against a Postgres.
- Chart templates;
helm template+helm lint;helm installon kind/minikube. ```