Skip to main content

Blueprint Anatomy

.larakube.json is the one file that describes your whole project. LaraKube CLI reads it during larakube up, heal, add, env, and the cloud commands β€” there's no hidden database and no separate config to keep in sync. Change this file, and you change how your app is deployed.

Don't let a big example scare you: you only fill in what you use. Most of the fields below are optional, and a brand-new project starts with just a handful.

🌱 The smallest blueprint​

This is a complete, valid blueprint:

.larakube.json
{
"name": "my-app",
"serverVariation": "frankenphp",
"phpVersion": "8.5",
"database": "sqlite",
"environments": {
"local": {},
"production": {}
}
}

That's enough to run locally and deploy. Everything else on this page is something you add as your needs grow.

πŸ“„ A fuller example​

Here's a more grown-up project β€” Filament admin, React frontend, a real database, search, caching, file storage, queues, websockets, and three environments (local, staging, production). Use it as a menu, not a checklist:

.larakube.json
{
"id": "9f3b7c1e-2d4a-4f9e-8c1d-1b2a3c4d5e6f",
"name": "acme-app",
"blueprints": ["filament"],
"serverVariation": "frankenphp",
"frontend": "react",
"phpVersion": "8.5",
"os": "alpine",
"email": "team@acme.example",
"additionalExtensions": ["gd", "exif"],
"features": [
"octane",
"scheduler",
"horizon",
"scout",
"reverb",
"ssr",
"ai",
"boost",
"mcp"
],
"scoutDriver": "meilisearch",
"packageManager": "npm",
"objectStorage": "minio",
"cacheDriver": "redis",
"database": "postgres",
"strategy": "single-node",
"environments": {
"local": {},
"staging": {
"ingress": "nginx",
"hosts": {
"web": "staging.acme.example",
"reverb": "ws-stg.acme.example",
"s3": "cdn-stg.acme.example"
}
},
"production": {
"managed": ["postgres", "redis"],
"resources": {
"default": {
"requests": { "cpu": "100m", "memory": "256Mi" },
"limits": { "cpu": "1", "memory": "1Gi" }
},
"horizon": {
"limits": { "memory": "2Gi" }
}
},
"hosts": {
"web": "acme.example",
"reverb": "ws.acme.example",
"s3": "cdn.acme.example"
}
}
},
"githubActions": true,
"withCompanions": true,
"provisionTestDb": true,
"lockedFiles": [
"Dockerfile.php",
".env.production",
".env.staging"
],
"watchPaths": [
"app",
"bootstrap",
"config",
"database",
"public",
"resources",
"routes",
"composer.lock",
".env"
]
}

Let's walk through it section by section.

🧬 Project identity​

"id": "9f3b7c1e-2d4a-4f9e-8c1d-1b2a3c4d5e6f",
"name": "acme-app",
"email": "team@acme.example"
  • id β€” A permanent UUID created the first time you run larakube init. It acts as the "DNA" of your project. If you move your project folder or rename it, LaraKube CLI still recognizes it by this ID. Your activity history and chat memory in the Console are linked to this ID, ensuring consistency even if your local file path changes. You never edit this by hand.
  • name β€” Your project's short name. LaraKube CLI uses it to label everything it creates in the cluster (each environment gets its own isolated space named acme-app-local, acme-app-production, and so on). The project folder name must match exactly.
  • email β€” Used when LaraKube CLI requests free HTTPS certificates from Let's Encrypt for your production site.

πŸ—οΈ Stack choices​

"blueprints": ["filament"],
"serverVariation": "frankenphp",
"frontend": "react",
"phpVersion": "8.5",
"os": "alpine",
"packageManager": "npm",
"additionalExtensions": ["gd", "exif"]
  • blueprints β€” Ready-made project types (a Filament admin, a Statamic CMS, etc.) that pull in sensible defaults for you. See Multi-Blueprint.
  • serverVariation β€” How PHP runs: frankenphp (recommended, great with Octane), fpm-nginx, or fpm-apache. This decides the Docker image LaraKube CLI builds.
  • frontend β€” react, vue, svelte, or livewire. Tells LaraKube CLI whether your app needs a separate Node build step and which Inertia/Echo packages to include.
  • phpVersion β€” The PHP version to pin (8.3, 8.4, 8.5).
  • os β€” alpine (small and fast to start) or bookworm (Debian β€” broader compatibility if you need certain native extensions).
  • packageManager β€” npm, pnpm, yarn, or bun, for building your frontend assets.
  • additionalExtensions β€” Extra PHP extensions to install. You usually only need one or two here β€” LaraKube CLI figures out the rest from your features and database choices.

πŸ’Ύ Data layer​

"database": "postgres",
"cacheDriver": "redis",
"scoutDriver": "meilisearch",
"objectStorage": "minio"

Your four main data choices: the database, cache, search engine, and file/object storage. Locally, LaraKube CLI runs each one for you (a Postgres, Redis, Meilisearch, and MinIO service), and it fills in the matching DB_*, CACHE_*, SCOUT_*, and AWS_* environment variables automatically β€” you don't wire those up by hand.

Need a second database (say, MongoDB for analytics alongside Postgres)? Use the plural "databases" array. The same plural form exists for "cacheDrivers", "scoutDrivers", and "objectStorages".

🧩 Features​

"features": [
"octane", "scheduler", "horizon",
"scout", "reverb", "ssr",
"ai", "boost", "mcp"
]

The Laravel extras you want turned on. This is one list for the whole project β€” you don't repeat it per environment.

LaraKube CLI already knows where each feature belongs, and these rules work for any environment name you create (staging, qa, …), not just local and production:

FeatureTurned on in
horizon, queues, scheduler, reverb, scout, octaneEvery environment
ssrEvery deployed (non-local) environment β€” locally, Vite already handles SSR for you
boost, ai, mcp, mailpitLocal only (these are dev tools)

You almost never need to change this. But if you want to (say, turn on Boost in a throwaway sandbox environment), each environment accepts an addFeatures list (turn something on) and an excludeFeatures list (turn something off).

🌍 Environments​

"environments": {
"local": {},
"staging": { … },
"production": { … }
}

This is the heart of the blueprint. Every project has a local environment; you add staging, production, or any name you like. Each environment's settings are optional β€” an empty {} (like local above) just means "use the sensible defaults." The settings you can set per environment:

  • ingress β€” Which traffic router sends visitors to your app. Defaults to Traefik (what LaraKube CLI installs for you). You can switch an environment to Nginx with "ingress": "nginx" β€” handy when an environment lives somewhere that already uses the Nginx ingress controller. (There's an AWS option too β€” see Advanced: managed Kubernetes.)
  • strategy β€” single-node or multi-node-ha for this environment. See Strategy below.
  • managed β€” Services you run outside the cluster in this environment. For example, ["postgres", "redis"] in production means "I'm using a managed database and Redis from my hosting provider." LaraKube CLI won't deploy its own Postgres/Redis there β€” it just points your app at the addresses in your .env.production.
  • plex β€” Services this environment gets from a shared Plex Commons (one cluster-wide Postgres/Redis/MinIO that several apps share, each with its own isolated database/bucket). A specialisation of managed: larakube plex:join writes the connection details into .env.<env>, and LaraKube CLI skips deploying its own copies. See Two Apps, One Server β†’ Plex.
  • hosts β€” The real domain names for this environment. See Per-service domains below.
  • cloud β€” How LaraKube CLI reaches the cluster for this environment β€” a VPS (SSH) or a managed kube-context. See Cloud connection below.
  • registry β€” The container registry CI/CD builds and pushes to (GHCR / Docker Hub). Required for managed clusters (which can't be SSH-sideloaded). See Container registry below.
  • storageClass, certManagerIssuer, and the managed-cluster knobs (namespace, serviceAccount, …) β€” only needed on managed Kubernetes. See Advanced: managed Kubernetes.
  • resources β€” Memory and CPU requests/limits for the app pods. Defines a default block plus specific role overrides (e.g. giving horizon more memory). LaraKube CLI manages this when you run larakube resources so you don't have to write the JSON by hand.
  • addFeatures / excludeFeatures β€” Turn a feature on or off just for this environment (rarely needed).

Local​

"local": {}

The simplest case. LaraKube CLI runs everything for you (Postgres, Redis, Meilisearch, MinIO), and every service answers on a friendly *.kube address with HTTPS already trusted β€” no setup required.

Staging​

"staging": {
"ingress": "nginx",
"hosts": {
"web": "staging.acme.example",
"reverb": "ws-stg.acme.example",
"s3": "cdn-stg.acme.example"
}
}

A real server, but still self-contained: LaraKube CLI runs Postgres, Redis, Meilisearch, and MinIO right inside this environment (cheap, isolated, easy to wipe and recreate). Here it uses the Nginx ingress instead of the default Traefik. (Note: The server connection details for this environment are stored separately in .larakube.local.json).

Production​

"production": {
"managed": ["postgres", "redis"],
"hosts": {
"web": "acme.example",
"reverb": "ws.acme.example",
"s3": "cdn.acme.example"
}
}

The grown-up environment. It uses the default Traefik ingress (no ingress line needed), and its database and Redis are managed β€” running on the hosting provider's managed services rather than inside the cluster. LaraKube CLI skips deploying its own Postgres/Redis here and trusts the addresses in your .env.production.

Notice MinIO is not in managed β€” we're still running our own file storage in production, and the s3 host exposes it at cdn.acme.example.

🌐 Per-service domains​

The hosts map is how you give each part of your app its own domain. LaraKube CLI picks a domain using three rules, in order:

  1. You set it explicitly. "reverb": "ws.acme.example" puts the Reverb websocket server on its own subdomain. This is the recommended pattern for websockets, file storage, and admin UIs.
  2. It's built from web. If a service has no domain of its own but you've set web, LaraKube CLI derives one β€” e.g. Meilisearch becomes meilisearch-acme.example.
  3. Local fallback. Locally, everything just answers on {service}-{name}.kube.

When you run larakube env qa, the wizard automatically asks about every service that can take its own subdomain (Reverb, file storage, Meilisearch, …), so you don't have to remember them.

A couple of things worth knowing:

  • Horizon and queues don't get their own domain. Horizon shows up at /horizon on your main site; queues have no UI.
  • Database/cache dashboards are local-only. LaraKube CLI won't expose a database console to the internet β€” those only show up locally. For production database access, use an SSH tunnel.

βš™οΈ Strategy & deployment​

"strategy": "single-node",
"githubActions": true,
"withCompanions": true,
"provisionTestDb": true
  • strategy β€” How your app is spread across servers:

    • single-node β€” one server (the classic, low-cost setup). This is the default.
    • multi-node-ha β€” several servers for high availability. App pods run stateless (per-pod storage, no shared volume), so state is externalized: uploads β†’ S3, sessions/cache β†’ Redis or the database. LaraKube CLI can set this from the cluster's node count when you record the target.

    This is the project-wide default, and any environment can override it (e.g. a single-node staging and a multi-node-ha production from the same blueprint). local is always single-node β€” it's just your one machine.

  • githubActions β€” Whether the GitHub Actions deploy workflow is set up. When true, larakube gha:configure wires up the secrets and creates the deploy workflow for you.

  • withCompanions β€” Whether to include the handy local-only dev apps (Mailpit, phpMyAdmin, RedisInsight, Grafana). Set to false for a leaner local setup.

  • provisionTestDb β€” When true, larakube test --db runs your tests against a real copy of your database engine instead of in-memory SQLite. Useful when your tests rely on database-specific features. LaraKube CLI sets this for you the first time you run larakube test --db.

☁️ Cloud connection (.larakube.local.json)​

Because .larakube.json is committed to version control, it holds no secrets and no infrastructure coordinates. Server IPs, managed Kubernetes contexts, and SSH keys are machine-specific (and potentially sensitive), so LaraKube CLI stores them in a completely separate, git-ignored file: .larakube.local.json.

You never edit this file by handβ€”you populate it using larakube cloud:configure:base.

When you do, it will generate an environment block with one of two shapes depending on your cluster:

A VPS (your own server, reached over SSH):

.larakube.local.json
{
"environments": {
"staging": {
"cloud": {
"ip": "203.0.113.20",
"user": "deploy",
"port": 22,
"key": "~/.ssh/id_rsa"
}
}
}
}
  • ip / user / port β€” how to SSH into the server. The kube-context is derived from the IP (larakube-<ip>).
  • key β€” path to your SSH private key (absolute, or ~-relative).

A managed cluster (DOKS / EKS / GKE / AKS β€” no SSH):

.larakube.local.json
{
"environments": {
"production": {
"cloud": {
"context": "do-sgp1-acme",
"provider": "doks"
}
}
}
}
  • context β€” the kube-context name your provider's CLI wrote into ~/.kube/config. LaraKube CLI targets it verbatim; there's no ip.
  • provider β€” doks, eks, gke, aks, etc. Drives sensible defaults (e.g. the env's storageClass).

Either shape also accepts:

  • arch β€” target node CPU architecture for the image build: amd64 or arm64. Leave it unset and LaraKube CLI auto-detects (over SSH for a VPS, from the cluster's nodes for a managed cluster), falling back to amd64. Set it to force a platform β€” e.g. "arch": "arm64" for a Raspberry Pi or an ARM/Graviton node.
Giving other people access

SSH is for you administering the box. To let teammates work with your apps, see Team Access β€” each person gets their own RBAC-scoped kubeconfig (no SSH, no server login), which works the same on a single VPS or a managed cluster.

πŸ“¦ Container registry​

When LaraKube CLI can't stream the image straight onto the node over SSH β€” i.e. any managed cluster, and any multi-node setup β€” it builds the image locally and pushes it to a registry, which the cluster then pulls. Configure that per environment with larakube cloud:configure:registry:

"environments": {
"production": {
"registry": { "provider": "ghcr", "image": "acme/acme-app" }
}
}
  • provider β€” ghcr (GitHub Container Registry) or dockerhub.
  • image β€” the repository path within the registry (e.g. owner/repo). Optional; defaults sensibly per provider.

The registry choice drives three things in lock-step: the image reference in your manifests, the login/build-push steps in the GitHub Actions workflow, and the image-pull secret created in the cluster. A plain VPS doesn't need this β€” larakube cloud:deploy sideloads the image over SSH instead.

GHCR images are private in LaraKube CLI β€” cloud:deploy creates the ghcr-login pull secret automatically from your larakube gha:login token, so there's no "make it public" step. (Docker Hub is the one registry where a public image is an option.)

πŸ”’ Maintenance fields​

"lockedFiles": [
"Dockerfile.php",
".env.production",
".env.staging"
],
"watchPaths": [
"app", "bootstrap", "config", "database",
"public", "resources", "routes",
"composer.lock", ".env"
]
  • lockedFiles β€” Files that larakube heal will leave alone. Use this when you've hand-edited a generated file (like a custom Dockerfile.php) and don't want it overwritten. Add one with larakube lock <path>, remove with larakube unlock <path>.
  • watchPaths β€” Folders larakube watch watches for changes to auto-reload your app during development. The defaults match a standard Laravel project; change them only if your project has an unusual layout.

πŸ€– System fields​

A couple of fields you'll mostly leave alone but might spot when reading other projects' blueprints:

  • isSystem β€” true only for LaraKube CLI's own internal projects (like the Console). Always false for your apps.
  • isScaffolding β€” A temporary flag that's only true while larakube new is setting a project up. It's stripped out when the file is saved, so you'll never see it in a committed .larakube.json.

🚦 Editing safely​

If you hand-edit .larakube.json:

  1. Check it. Run larakube heal --force β€” it complains loudly if something's malformed.
  2. Re-generate. larakube heal rewrites everything under .infrastructure/k8s/ to match. Your running app keeps running; the next larakube up catches it up.
  3. Commit the blueprint first. The blueprint is the source of truth; the generated files just follow from it.

For most changes β€” adding a feature, switching the ingress, adding an environment β€” prefer the dedicated commands (larakube add, larakube env, larakube cloud:configure) over hand-editing. They validate things for you.

🧩 Advanced: managed Kubernetes​

Skip this unless you're deploying to a managed Kubernetes service (DigitalOcean DOKS, AWS EKS, Google GKE, Azure AKS) instead of your own VPS. If you're on a regular server with Traefik or Nginx, you'll never need any of it. For a guided walkthrough, see the DOKS Quickstart.

A managed environment is just one whose cloud block uses context/provider (see Cloud connection) and whose images come from a registry. On top of that, each environment optionally accepts these knobs β€” every one defaults to the normal VPS behavior, so they only do something when you set them:

  • storageClass β€” the StorageClass for this env's volumes. Usually set for you from provider (DOKS β†’ do-block-storage, EKS β†’ gp3, GKE β†’ standard, AKS β†’ managed-premium). Unset = the cluster default.
  • certManagerIssuer β€” turn on automatic HTTPS via cert-manager: the name of a ClusterIssuer (e.g. "letsencrypt-prod"). LaraKube CLI adds the issuer annotation to the ingress. Requires cert-manager already installed on the cluster. (On a LaraKube CLI-provisioned DOKS cluster, Traefik + Let's Encrypt is installed for you, so you use ingressAnnotations instead β€” see below.)
  • namespace β€” deploy into an existing cluster space instead of LaraKube CLI's default {name}-{environment}.
  • serviceAccount (and serviceAccountAnnotations) β€” run your app under a specific cluster identity, e.g. to give it cloud permissions (EKS IRSA, GKE Workload Identity).
  • imagePullSecret / omitImagePullSecret β€” change or drop the credential used to pull your container image (some clusters pull images using the server's own cloud role, so no secret is needed).
  • ingressAnnotations β€” extra settings passed straight through to your ingress/load balancer (for example, a TLS certificate ID, a firewall group, or β€” on a DOKS Traefik cluster β€” traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt to fetch a cert for this app).

A managed production therefore looks like this in your main blueprint:

.larakube.json
"production": {
"registry": { "provider": "ghcr", "image": "acme/acme-app" },
"storageClass": "do-block-storage",
"hosts": { "web": "acme.example", "s3": "cdn.acme.example" },
"plex": ["postgres", "redis", "minio"]
}

(Remember: the "cloud": { "context": "do-sgp1-acme" } block for this environment is securely generated in .larakube.local.json when you run larakube cloud:configure:base).

If any of the terms above are unfamiliar, that's a sign you don't need this section yet.

One ingress IP, many hosts

On a managed cluster every host β€” your app, plus any shared S3/Commons host β€” resolves to the same ingress LoadBalancer IP. Point one DNS A record ("anchor") at that IP, then CNAME every host to the anchor. Each new app you add is just another CNAME, and if the IP ever changes you update a single record. larakube cloud:provision:doks, plex:init, and cloud:deploy all print this guidance with your real IP.

🧠 Console Memory & Sync​

LaraKube treats your .larakube.json as infrastructure as code, but it also pairs this with a global SQLite database maintained by the LaraKube Console. This provides a unified history for all your projects without scattering logs across different folders.

Whenever you run a command via the CLI, it automatically "calls home" to the Console API to register the event. This real-time sync tracks:

  • Operational Verbs: Every up, down, heal, or purge command.
  • Architectural Evolution: When you add features or swap databases via the CLI or UI.
  • AI Diagnostics: Full reports from the doctor command and AI-powered fix history.

This ensures your visual dashboard and AI agents always have an accurate, up-to-date memory of your project's evolution, even if you prefer working entirely in the terminal.