{"id":35828926,"url":"https://github.com/narvanalabs/control-plane","last_synced_at":"2026-01-24T15:12:00.466Z","repository":{"id":331101104,"uuid":"1119939529","full_name":"narvanalabs/control-plane","owner":"narvanalabs","description":"Central orchestration component of Narvana","archived":false,"fork":false,"pushed_at":"2026-01-08T22:25:16.000Z","size":1824,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-01-13T21:24:47.087Z","etag":null,"topics":["orchestrator-engine"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/narvanalabs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-20T06:27:27.000Z","updated_at":"2026-01-08T22:25:12.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/narvanalabs/control-plane","commit_stats":null,"previous_names":["narvanalabs/control-plane"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/narvanalabs/control-plane","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narvanalabs%2Fcontrol-plane","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narvanalabs%2Fcontrol-plane/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narvanalabs%2Fcontrol-plane/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narvanalabs%2Fcontrol-plane/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/narvanalabs","download_url":"https://codeload.github.com/narvanalabs/control-plane/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narvanalabs%2Fcontrol-plane/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28730316,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T10:24:43.181Z","status":"ssl_error","status_checked_at":"2026-01-24T10:24:36.112Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["orchestrator-engine"],"created_at":"2026-01-07T21:19:47.846Z","updated_at":"2026-01-24T15:12:00.458Z","avatar_url":"https://github.com/narvanalabs.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Narvana Control Plane\n\n[![Go Version](https://img.shields.io/badge/Go-1.24+-00ADD8?style=flat\u0026logo=go)](https://go.dev/)\n[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)\n[![Podman](https://img.shields.io/badge/Podman-Container_Runtime-892CA0?style=flat\u0026logo=podman\u0026logoColor=white)](https://podman.io/)\n[![Nix](https://img.shields.io/badge/Nix-Flakes-5277C3?style=flat\u0026logo=nixos\u0026logoColor=white)](https://nixos.org/)\n[![gRPC](https://img.shields.io/badge/gRPC-Protocol-244c5a?style=flat\u0026logo=google\u0026logoColor=white)](https://grpc.io/)\n[![Tests](https://img.shields.io/badge/Tests-Passing-success?style=flat)](#running-tests)\n\nA self-hosted Platform-as-a-Service (PaaS) control plane built with Go, designed for deploying and managing applications using Nix flakes and OCI containers. Narvana provides a modern, Nix-native approach to application deployment with automatic build detection, multi-tenancy support, and distributed node management.\n\n## Features\n\n- **Nix-Native Builds**: First-class support for Nix flakes with automatic flake generation for Go, Rust, Node.js, Python, and database services\n- **Dual Build Modes**: Support for both pure Nix store paths and OCI container images\n- **Auto-Detection**: Automatic language and framework detection with intelligent build strategy selection\n- **Multi-Tenancy**: Organization-based resource isolation with role-based access control\n- **Distributed Scheduling**: Intelligent node placement based on health, capacity, and cache locality\n- **gRPC Node Communication**: Bidirectional streaming for real-time deployment commands and status updates\n- **Binary Caching**: Integration with [Attic](https://github.com/zhaofengli/attic) for Nix binary cache acceleration\n- **Secrets Management**: SOPS-based encryption for application secrets using age keys\n- **Custom Domains**: Support for custom domain mappings with wildcard certificates\n- **Real-time Logs**: SSE-based log streaming for builds and runtime\n\n## Architecture\n\n### System Overview\n\n```mermaid\ngraph TB\n    subgraph \"Control Plane\"\n        WebUI[\"Web UI\u003cbr/\u003e:8090\"]\n        API[\"API Server\u003cbr/\u003e:8080 HTTP\u003cbr/\u003e:9090 gRPC\"]\n        Worker[\"Build Worker\"]\n        Scheduler[\"Scheduler\"]\n    end\n    \n    subgraph \"Data Layer\"\n        PG[(PostgreSQL)]\n        Attic[\"Attic\u003cbr/\u003eBinary Cache\"]\n    end\n    \n    subgraph \"Compute Nodes\"\n        Node1[\"Node Agent 1\u003cbr/\u003e(Podman)\"]\n        Node2[\"Node Agent 2\u003cbr/\u003e(Podman)\"]\n        Node3[\"Node Agent N\u003cbr/\u003e(Podman)\"]\n    end\n    \n    WebUI --\u003e API\n    API --\u003e PG\n    API --\u003e Scheduler\n    Worker --\u003e PG\n    Worker --\u003e Attic\n    Scheduler --\u003e PG\n    \n    API \u003c--\u003e|\"gRPC\u003cbr/\u003eBidirectional\"| Node1\n    API \u003c--\u003e|\"gRPC\u003cbr/\u003eBidirectional\"| Node2\n    API \u003c--\u003e|\"gRPC\u003cbr/\u003eBidirectional\"| Node3\n    \n    Node1 --\u003e Attic\n    Node2 --\u003e Attic\n    Node3 --\u003e Attic\n```\n\n### Deployment Flow\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant API\n    participant Queue\n    participant Worker\n    participant Scheduler\n    participant Node\n    participant Attic\n    \n    User-\u003e\u003eAPI: POST /deploy\n    API-\u003e\u003eQueue: Enqueue build job\n    API--\u003e\u003eUser: 202 Accepted\n    \n    Worker-\u003e\u003eQueue: Dequeue job\n    Worker-\u003e\u003eWorker: Detect strategy\n    Worker-\u003e\u003eWorker: Generate flake\n    Worker-\u003e\u003eWorker: Build artifact\n    Worker-\u003e\u003eAttic: Push to cache\n    Worker-\u003e\u003eAPI: Update status: built\n    \n    Scheduler-\u003e\u003eAPI: Poll built deployments\n    Scheduler-\u003e\u003eScheduler: Select optimal node\n    Scheduler-\u003e\u003eNode: Deploy command (gRPC)\n    \n    Node-\u003e\u003eAttic: Pull artifact\n    Node-\u003e\u003eNode: Start container\n    Node-\u003e\u003eAPI: Status: running\n```\n\n### Build Strategy Selection\n\n```mermaid\nflowchart TD\n    Start([Source Code]) --\u003e HasFlake{Has flake.nix?}\n    \n    HasFlake --\u003e|Yes| UseFlake[Use Flake Strategy]\n    HasFlake --\u003e|No| HasDockerfile{Has Dockerfile?}\n    \n    HasDockerfile --\u003e|Yes| UseDockerfile[Use Dockerfile Strategy]\n    HasDockerfile --\u003e|No| DetectLang[Detect Language]\n    \n    DetectLang --\u003e Go{Go?}\n    DetectLang --\u003e Rust{Rust?}\n    DetectLang --\u003e Node{Node.js?}\n    DetectLang --\u003e Python{Python?}\n    \n    Go --\u003e|Yes| AutoGo[auto-go]\n    Rust --\u003e|Yes| AutoRust[auto-rust]\n    Node --\u003e|Yes| AutoNode[auto-node]\n    Python --\u003e|Yes| AutoPython[auto-python]\n    \n    Go --\u003e|No| Rust\n    Rust --\u003e|No| Node\n    Node --\u003e|No| Python\n    Python --\u003e|No| Nixpacks[Use Nixpacks]\n    \n    UseFlake --\u003e Build([Build Artifact])\n    UseDockerfile --\u003e Build\n    AutoGo --\u003e Build\n    AutoRust --\u003e Build\n    AutoNode --\u003e Build\n    AutoPython --\u003e Build\n    Nixpacks --\u003e Build\n```\n\n### Deployment State Machine\n\n```mermaid\nstateDiagram-v2\n    [*] --\u003e pending: Create deployment\n    pending --\u003e building: Worker picks up\n    building --\u003e built: Build succeeds\n    building --\u003e failed: Build fails\n    built --\u003e scheduled: Node assigned\n    scheduled --\u003e starting: Agent starts container\n    starting --\u003e running: Container healthy\n    starting --\u003e failed: Start fails\n    running --\u003e stopping: Stop requested\n    stopping --\u003e stopped: Container stopped\n    running --\u003e failed: Container crashes\n    stopped --\u003e [*]\n    failed --\u003e [*]\n```\n\n### Data Model\n\n```mermaid\nerDiagram\n    Organization ||--o{ App : contains\n    Organization ||--o{ User : has_members\n    User ||--o{ App : owns\n    App ||--o{ Service : contains\n    App ||--o{ Secret : has\n    App ||--o{ Domain : has\n    Service ||--o{ Deployment : has\n    Deployment ||--o| BuildJob : triggers\n    Deployment ||--o| Node : runs_on\n    Node ||--o{ Deployment : hosts\n    \n    Organization {\n        uuid id PK\n        string name\n        string slug UK\n        string description\n    }\n    \n    App {\n        uuid id PK\n        uuid org_id FK\n        string owner_id FK\n        string name\n        int version\n        jsonb services\n    }\n    \n    Deployment {\n        uuid id PK\n        uuid app_id FK\n        string service_name\n        int version\n        string status\n        uuid node_id FK\n        string artifact\n    }\n    \n    Node {\n        uuid id PK\n        string hostname\n        string address\n        boolean healthy\n        jsonb resources\n    }\n```\n\n## Requirements\n\n- [Nix](https://nixos.org/download.html) with flakes enabled\n- Go 1.24+\n- PostgreSQL 15+\n- Podman (for OCI builds and container runtime)\n\n## Quick Start\n\n### Using Nix (Recommended)\n\n```bash\n# Clone the repository\ngit clone https://github.com/narvanalabs/control-plane.git\ncd control-plane\n\n# Enter the development shell (starts PostgreSQL automatically)\nnix develop\n\n# Run database migrations\nmake migrate-up\n\n# Start all services (API, Worker, Web UI, Attic cache)\nmake dev-all\n```\n\nThe development environment will be available at:\n- Web UI: http://localhost:8090\n- API: http://localhost:8080\n- gRPC: localhost:9090\n\n### Manual Setup\n\n```bash\n# Install dependencies\ngo mod download\n\n# Set required environment variables\nexport DATABASE_URL=\"postgres://user:pass@localhost:5432/narvana?sslmode=disable\"\nexport JWT_SECRET=\"your-secret-key-minimum-32-characters-long\"\n\n# Run migrations\nmake migrate-up\n\n# Build and run\nmake build\n./bin/api \u0026\n./bin/worker \u0026\n./bin/web \u0026\n```\n\n## Configuration\n\nConfiguration is managed through environment variables:\n\n### Core Settings\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `DATABASE_URL` | PostgreSQL connection string | `postgres://localhost:5432/narvana?sslmode=disable` |\n| `JWT_SECRET` | Secret key for JWT tokens (min 32 chars) | Required |\n| `JWT_EXPIRY` | Token expiration duration | `24h` |\n| `API_PORT` | HTTP API server port | `8080` |\n| `GRPC_PORT` | gRPC server port | `9090` |\n| `API_HOST` | API server bind address | `0.0.0.0` |\n\n### Build Worker Settings\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `WORKER_WORKDIR` | Build working directory | `/tmp/narvana-builds` |\n| `WORKER_MAX_CONCURRENCY` | Max concurrent builds | `4` |\n| `BUILD_TIMEOUT` | Build timeout duration | `30m` |\n| `PODMAN_SOCKET` | Podman socket path | `unix:///run/user/1000/podman/podman.sock` |\n| `ATTIC_ENDPOINT` | Attic binary cache URL | `http://localhost:5000` |\n\n### Scheduler Settings\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `SCHEDULER_HEALTH_THRESHOLD` | Node health check threshold | `30s` |\n| `SCHEDULER_MAX_RETRIES` | Max deployment retries | `5` |\n| `SCHEDULER_RETRY_BACKOFF` | Retry backoff duration | `5s` |\n| `SCHEDULER_DEPLOYMENT_TIMEOUT` | Deployment scheduling timeout | `30m` |\n\n### Secrets Encryption (SOPS)\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `SOPS_AGE_PUBLIC_KEY` | Age public key for encryption | Optional |\n| `SOPS_AGE_PRIVATE_KEY` | Age private key for decryption | Optional |\n\n## Project Structure\n\n```\n.\n├── api/proto/              # gRPC protocol definitions\n├── cmd/\n│   ├── api/                # API server entry point\n│   ├── web/                # Web UI server entry point\n│   └── worker/             # Build worker entry point\n├── internal/\n│   ├── api/                # HTTP API handlers and middleware\n│   ├── auth/               # Authentication and RBAC\n│   ├── builder/            # Build system (Nix, OCI, strategies)\n│   ├── cleanup/            # Resource cleanup services\n│   ├── grpc/               # gRPC server and node management\n│   ├── models/             # Domain models\n│   ├── queue/              # Build job queue\n│   ├── scheduler/          # Deployment scheduler\n│   ├── secrets/            # SOPS secrets management\n│   ├── store/              # Database access layer\n│   └── validation/         # Input validation services\n├── migrations/             # SQL migrations\n├── pkg/\n│   ├── config/             # Configuration loading\n│   └── logger/             # Structured logging\n├── web/                    # Web UI (templ templates)\n├── flake.nix               # Nix flake for development\n└── Makefile                # Build and development commands\n```\n\n## Build Strategies\n\nNarvana supports multiple build strategies:\n\n| Strategy | Description | Build Type |\n|----------|-------------|------------|\n| `flake` | Use existing `flake.nix` in repository | Nix or OCI |\n| `auto-go` | Auto-generate flake for Go projects | Nix or OCI |\n| `auto-rust` | Auto-generate flake for Rust projects | Nix or OCI |\n| `auto-node` | Auto-generate flake for Node.js projects | Nix or OCI |\n| `auto-python` | Auto-generate flake for Python projects | Nix or OCI |\n| `auto-database` | Auto-generate flake for database services | Nix |\n| `dockerfile` | Build from Dockerfile | OCI only |\n| `nixpacks` | Use Nixpacks for detection and building | OCI only |\n| `auto` | Automatic strategy detection | Varies |\n\n## API Overview\n\n### Authentication\n\n```bash\n# Register (first user becomes owner)\ncurl -X POST http://localhost:8080/auth/register \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"admin@example.com\", \"password\": \"secure-password\"}'\n\n# Login\ncurl -X POST http://localhost:8080/auth/login \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"email\": \"admin@example.com\", \"password\": \"secure-password\"}'\n```\n\n### Apps and Services\n\n```bash\n# Create an app\ncurl -X POST http://localhost:8080/v1/apps \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\": \"my-app\", \"description\": \"My application\"}'\n\n# Create a service\ncurl -X POST http://localhost:8080/v1/apps/$APP_ID/services \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"api\",\n    \"source_type\": \"git\",\n    \"git_repo\": \"github.com/myorg/myrepo\",\n    \"build_strategy\": \"auto-go\"\n  }'\n\n# Deploy a service\ncurl -X POST http://localhost:8080/v1/apps/$APP_ID/services/api/deploy \\\n  -H \"Authorization: Bearer $TOKEN\"\n```\n\n### Node Management\n\n```bash\n# List nodes\ncurl http://localhost:8080/v1/nodes \\\n  -H \"Authorization: Bearer $TOKEN\"\n\n# Register a node (from node agent)\ncurl -X POST http://localhost:8080/v1/nodes/register \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"hostname\": \"node-1\",\n    \"address\": \"192.168.1.100\",\n    \"grpc_port\": 9090\n  }'\n```\n\n## Development\n\n### Running Tests\n\n```bash\n# Run all tests\nmake test\n\n# Run unit tests only\nmake test-unit\n\n# Run property-based tests\nmake test-property\n```\n\n### Code Generation\n\n```bash\n# Generate protobuf files\nmake proto\n\n# Generate templ templates (for web UI)\ncd web \u0026\u0026 templ generate\n```\n\n### Database Migrations\n\n```bash\n# Apply migrations\nmake migrate-up\n\n# Check migration status\nmake db-status\n```\n\n### Linting\n\n```bash\nmake lint\n```\n\n### Testing `scripts/install.sh` in a container\n\nYou can iterate on the installer locally without pushing to GitHub by running it inside a disposable container.\n\n**Using Docker:**\n\n```bash\ncd control-plane\n\ndocker run --rm -it --privileged \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  -v \"$PWD:/repo:ro\" \\\n  docker:24-dind sh -c \"apk add curl bash openssl \u0026\u0026 bash /repo/scripts/install.sh\"\n```\n\n**Using Podman:**\n\n```bash\ncd control-plane\n\npodman run --rm -it --privileged \\\n  -v /var/run/podman/podman.sock:/var/run/docker.sock \\\n  -v \"$PWD:/repo:ro\" \\\n  docker.io/library/docker:24-cli sh -c \"apk add curl bash openssl \u0026\u0026 bash /repo/scripts/install.sh\"\n```\n\nThe installer only requires Docker/Podman and curl - it pulls pre-built container images rather than building from source.\n\n## Supported Databases\n\nNarvana can provision managed database services:\n\n| Type | Supported Versions | Default Port |\n|------|-------------------|--------------|\n| PostgreSQL | 14, 15, 16 | 5432 |\n| MySQL | 8.0 | 3306 |\n| MariaDB | 10, 11 | 3306 |\n| MongoDB | 6.0, 7.0 | 27017 |\n| Redis | 6, 7 | 6379 |\n| SQLite | 3 | N/A |\n\n## Resource Specifications\n\nServices can specify resource limits directly:\n\n```json\n{\n  \"resources\": {\n    \"cpu\": \"0.5\",\n    \"memory\": \"512Mi\"\n  }\n}\n```\n\n\u003e **Note**: The predefined resource tiers (`nano`, `small`, `medium`, `large`, `xlarge`) are deprecated and will be removed in a future version. Use direct `resources` specification instead.\n\n## Cleanup and Maintenance\n\nNarvana includes automatic cleanup services:\n\n```bash\n# Manual cleanup endpoints (admin only)\ncurl -X POST http://localhost:8080/v1/admin/cleanup/containers \\\n  -H \"Authorization: Bearer $TOKEN\"\n\ncurl -X POST http://localhost:8080/v1/admin/cleanup/images \\\n  -H \"Authorization: Bearer $TOKEN\"\n\ncurl -X POST http://localhost:8080/v1/admin/cleanup/nix-gc \\\n  -H \"Authorization: Bearer $TOKEN\"\n\ncurl -X POST http://localhost:8080/v1/admin/cleanup/deployments \\\n  -H \"Authorization: Bearer $TOKEN\"\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## License\n\nThis project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.\n\n---\n\n\u003cp align=\"center\"\u003e\n  Built with ❄️ Nix and 💙 Go\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnarvanalabs%2Fcontrol-plane","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnarvanalabs%2Fcontrol-plane","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnarvanalabs%2Fcontrol-plane/lists"}