# Contributing to Lucity Lucity is still in early development. We're not accepting external contributions yet - the architecture is moving fast and we'd rather not waste your time with PRs that conflict with in-flight changes. That said, we'd love to hear from you: - **Bug reports:** open an issue using the bug report template - **Feature requests:** open an issue using the feature request template - **Questions:** use GitHub Discussions The rest of this guide covers the local development setup if you want to explore the codebase. ## Architecture in one breath Lucity is a single Go control-plane binary (**conductor**) plus a Vue 3 **dashboard** and a separate **cashier** billing service. The conductor serves the GraphQL API, spawns build jobs, deploys workloads via Helm, and receives GitHub webhooks. Deployment state lives in the cluster as Helm releases. ## Prerequisites - [Go 1.27+](https://go.dev/dl/) - [Node.js 20+](https://nodejs.org/) - [Docker](https://docs.docker.com/get-docker/) - [Minikube](https://minikube.sigs.k8s.io/docs/start/) - [Helm](https://helm.sh/docs/intro/install/) - [kubectl](https://kubernetes.io/docs/tasks/tools/) - [crane](https://github.com/google/go-containerregistry/tree/main/cmd/crane) (image push) - [air](https://github.com/air-verse/air) (hot reload) - A [GitHub App](https://docs.github.com/en/apps/creating-github-apps) configured for OAuth ## Getting Started ### 1. Create the cluster ```sh make minikube ``` Starts minikube with `--insecure-registry "10.96.0.0/12"` so Docker on the node trusts any ClusterIP-based registry over HTTP. This covers the entire Kubernetes service CIDR. See [minikube registry docs](https://minikube.sigs.k8s.io/docs/handbook/registry/#enabling-insecure-registries). ### 2. Deploy infrastructure ```sh make infra ``` Installs the Gateway API CRDs and Envoy Gateway, then deploys the `lucity-infra` chart into the `lucity-system` namespace: the OCI registry (Zot), the PostgreSQL operator (CloudNativePG), the observability stack, and a Gateway resource. ### 3. Set up local DNS ```sh make dns ``` Configures [dnsmasq](https://thekelleys.org.uk/dnsmasq/doc.html) so `*.lucity.local` resolves to `127.0.0.1`. Run once; survives reboots. Requires Homebrew (installs dnsmasq if not present). This lets you access deployed services by hostname, e.g. `http://myapp.lucity.local:8880`. ### 4. Port-forward infrastructure ```sh make infra-forward ``` Exposes infrastructure on localhost: | Service | Local Port | |---------|-----------| | Zot (OCI registry) | `:5000` | | Envoy Gateway | `:8880` | | Logto (admin) | `:3002` | | VictoriaMetrics | `:8428` | | Grafana | `:3000` | Deployed services with a configured hostname are accessible at `http://.lucity.local:8880` via Envoy Gateway and Gateway API HTTPRoutes. ### 5. Configure services The conductor and cashier each ship a `.env.example`. Copy and fill in the values: ```sh cp services/conductor/.env.example services/conductor/.env cp services/cashier/.env.example services/cashier/.env ``` Key configuration: | Service | Required Variables | |---------|-------------------| | Conductor | GitHub App (`GITHUB_APP_ID`, `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, private key), OIDC/Logto (`OIDC_*`, `LOGTO_*`), registry URLs, `WORKLOAD_DOMAIN` | | Cashier | `STRIPE_*`, `LOGTO_*`, `VICTORIA_METRICS_URL` (only needed when working on metering) | Point the conductor at Zot's fixed ClusterIP (assigned in `deployments/minikube/values.yaml`, e.g. `10.96.100.50:5000`) for the image refs it writes into Helm values, so kubelet can pull them. > **Why a ClusterIP, not a DNS name?** Docker on minikube uses the host DNS resolver, not CoreDNS. Cluster-internal DNS names like `*.svc.cluster.local` don't resolve for image pulls. The fixed ClusterIP works because `--insecure-registry` already covers the service CIDR. Billing is optional in dev: leave `STRIPE_SECRET_KEY` unset on the conductor to run without it. ### 6. Start all services ```sh make dev ``` Conductor, cashier, and dashboard start with hot reload (air for the Go services, Vite for the dashboard). Dashboard at http://localhost:5173, GraphQL playground at http://localhost:8080/playground. ## Quick Reference ```sh make minikube # 1. Create cluster (one-time) make infra # 2. CRDs + Envoy Gateway + lucity-infra chart make dns # 3. Wildcard DNS for *.lucity.local (one-time) make infra-forward # 4. Port-forward infrastructure make dev # 5. Start services with hot reload ``` ## Services | Service | Port | Protocol | Purpose | |---------|------|----------|---------| | Conductor | 8080 (HTTP), 9004 (webhook) | HTTP | Unified control plane: GraphQL API, Helm release management, build orchestration, GitHub webhooks, Stripe billing | | Cashier | none | outbound only | Usage metering into Stripe, monthly plan credits | | Dashboard | 5173 | HTTP | Vue 3 SPA for project and environment management | `builder` is a separate image (`ghcr.io/zeitlos/lucity/builder`) that the conductor runs inside a per-build Kubernetes Job, not a long-running service. ## Makefile Targets | Target | Description | |--------|-------------| | `make minikube` | Create minikube cluster with insecure registry config | | `make infra` | Install CRDs + Envoy Gateway + deploy the `lucity-infra` chart | | `make dns` | Set up wildcard DNS for `*.lucity.local` (one-time) | | `make infra-forward` | Port-forward infrastructure to localhost | | `make dev` | Start all services with hot reload | | `make dev-` | Start one service (e.g. `make dev-conductor`) | | `make dev-logs` | Tail all service logs | | `make dev-stop` | Stop all services | | `make build` | Build all Go services | | `make generate-graphql` | Regenerate GraphQL resolvers | | `make lint` | Run dashboard linter | | `make infra-down` | Uninstall infrastructure from cluster | ## Further Reading - [Concepts](https://lucity.cloud/docs/projects): projects, services, environments - [Self-Hosting](https://lucity.cloud/docs/self-hosting): hosting Lucity on your own hardware