cipi.yml · config as code

Manage Laravel apps with cipi.yml

By · Last updated: · free to read, no paywall

The state an app expects on the server — aliases, PHP, workers, health, backups — usually lives in a panel or in someone's head. Since Cipi 5.1 it can live in a cipi.yml at the root of the repository, reviewed in the same pull request as the code that depends on it. This guide is the practical loop: generate, plan, apply, then opt in so every deploy reconciles.

In this guide
  1. Why the file belongs next to the code
  2. Start from the live server
  3. The commands you will use
  4. What you can declare
  5. A complete file
  6. Plan, then apply
  7. A week of real changes
  8. Opt in after every deploy
  9. What the file will not touch
  10. Safe to accept over Git
  11. FAQ

Why the file belongs next to the code

A release that adds a queued job without a worker is half-shipped. A rollback that leaves last week's upload_max_filesize is half-rolled-back. Server state usually lives somewhere else: a panel, a wiki page, or the memory of whoever set the box up.

A cipi.yml committed next to the Laravel application makes that state part of the same pull request as the code that depends on it. The file is declarative: it describes the end state, not the steps. Cipi reads what the server has, compares it with the file and shows you the difference before touching anything.

The product page cipi.yml — config that ships with the code is the overview. The full schema lives in Docs → Deploy → cipi.yml. This guide is the day-to-day loop for one app.

Start from the live server

You do not have to write the file by hand. On a Cipi 5.1+ box, print the app's current configuration — aliases, PHP version and per-app settings, extra databases, queue workers read back out of Supervisor, Horizon, Reverb, the scheduler and the backup profiles the app owns — as a ready-to-commit file:

$ cipi yml generate myapp > cipi.yml
$ cipi yml plan myapp                  # reports nothing to do

Commit that file at the repository root. Cipi looks it up in current/cipi.yml, then current/cipi.yaml, then shared/cipi.yml — override with --file=<path> if you keep it elsewhere.

Prefer a blank, fully commented template? cipi yml example myapp prints one with placeholder databases and profiles already inside that app's namespace, so the template validates as-is.

The commands you will use

Command What it does
cipi yml generate Prints the app's configuration as it stands on the server, ready to commit
cipi yml example A blank commented template, namespaced to the app so it validates as-is
cipi yml validate Parses the file and checks every value against the schema. Changes nothing
cipi yml plan The diff: every alias, worker, database, setting and profile that would be added, changed or removed
cipi yml apply Applies the plan. Add --yes for scripts and CI
cipi yml auto on / off / status — reconcile after every successful deploy
$ cipi yml generate myapp
$ cipi yml example myapp
$ cipi yml validate myapp
$ cipi yml plan myapp
$ cipi yml apply myapp [--yes]
$ cipi yml auto myapp on|off|status

What you can declare

Seven areas. You do not have to declare all of them — only the sections you write are reconciled.

A complete file

This is what a production app typically ships. You can generate most of it; the comments are for the pull request.

version: 1

app:
  php: "8.5"
  aliases:
    - "www.myapp.com"
    - "*.myapp.com"
  ini:
    upload_max_filesize: 50M
    post_max_size: 60M
    memory_limit: 512M

databases:
  - name: myapp_reporting
  - name: myapp_analytics
    engine: pgsql

workers:
  horizon: false
  reverb: false
  queues:
    - queue: default
      processes: 2
    - queue: emails
      processes: 1
      tries: 5
      timeout: 300

schedule: true

health:
  url: "https://myapp.com/up"
  expect: 200

backup:
  profiles:
    - name: myapp-db
      scope: db
      databases: ["myapp", "myapp_*"]
      exclude_tables: ["*.jobs", "*.telescope_*"]
      every: 30m
      keep: 48
      destinations: [local]
    - name: myapp-nightly
      scope: all
      cron: "0 2 * * *"
      keep_days: 14
      destinations: [s3]
      encrypt: true

Plan, then apply

Nothing changes until you look at the diff. cipi yml plan myapp lists every alias, worker, database, setting and profile that would be added, changed or removed. Read it the way you read a pull request.

$ cipi yml validate myapp
$ cipi yml plan myapp
$ cipi yml apply myapp                 # asks for confirmation
$ cipi yml apply myapp --yes           # scripts and CI

A file that fails validation is rejected as a whole and never partially applied. That is the same fail-closed path a deploy uses when yml auto is on: email yml_fail, no half-state on the box.

A week of real changes

Treat the file like application code. Each of these is a one-line (or one-block) commit, reviewed, then applied — or applied automatically after the release if yml auto is already on.

PHP 8.5 must already be installed (cipi php install 8.5) before you pin app.php to it. The file will not install a runtime that is not on the box.

Opt in after every deploy

Deploys ignore the file until you say otherwise. That is deliberate: a first commit of cipi.yml should not surprise production.

$ cipi yml auto myapp on       # reconcile after every successful deploy
$ cipi yml auto myapp status
$ cipi yml auto myapp off      # back to manual apply

With the opt-in given, every successful deploy reconciles — from both cipi deploy and the Git webhook. A release that carries no cipi.yml is a quiet no-op. A file that fails validation is reported by email (yml_fail) and never applied halfway. A successful reconcile fires yml_apply.

What the file will not touch

Safe to accept over Git

The file arrives from a repository, so anyone who can commit controls its contents. The schema is fail-closed throughout:

Once yml auto is on, anyone who can push to that repository can change the app's aliases, PHP settings, workers, healthcheck and backup profiles. That is the point of configuration-as-code — treat write access to the repo accordingly.

Ship the file with the next release

Generate what the server already has, commit it, read the plan, then turn yml auto on when you trust the loop. The overview and the full schema are one click away.

wget -O - https://cipi.sh/setup.sh | bash

Frequently asked questions

Do I have to write cipi.yml by hand?

No. cipi yml generate <app> prints the app's current server configuration as a ready-to-commit file, and cipi yml example prints a blank commented template if you would rather start from scratch.

Does a deploy apply the file automatically?

Only after you opt in with cipi yml auto <app> on. Until then deploys ignore the file and you reconcile manually with plan and apply.

What happens to things the file does not mention?

They are left alone. Only the sections you declare are reconciled — with one deliberate exception: the alias list is authoritative, so removing an alias from the file removes it from the server.

Can a commit break my server?

The schema carries no shell commands and no include paths, an app can only touch its own databases and backup profiles, and a file that fails validation is rejected as a whole rather than applied halfway. Deploys ignore the file entirely until you turn yml auto on.

Does it work with the Git webhook?

Yes. With cipi yml auto on, both cipi deploy and a webhook-triggered deploy reconcile after a successful release.

Keep reading