Since Cipi 5.1

cipi.yml — config that ships with the code.

One file at the root of your repository describes the state an app expects on the server: domain aliases, PHP version and per-app settings, extra databases, queue workers, the scheduler, its healthcheck and its backup profiles. Review it in a pull request, ship it with the release, and let cipi yml apply reconcile the server with it.

7 areasAliases, PHP, php.ini, databases, workers, scheduler, health and backups.
plan → applyNothing changes until you look at the diff and say yes.
Fail-closedScoped to one app, no shell commands anywhere in the schema.

Server configuration, reviewed like code

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 your Laravel application makes that state part of the same pull request as the code that depends on it. A release that adds a queue also adds its worker. A release that needs a bigger upload_max_filesize carries it. Roll the code back, and the configuration rolls back with 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 commands

bash
$ cipi yml generate myapp          # this app's current config, as a cipi.yml
$ cipi yml example myapp           # blank commented template, in myapp's namespace
$ cipi yml validate myapp          # parse and check, change nothing
$ cipi yml plan myapp              # show exactly what would change
$ cipi yml apply myapp [--yes]     # apply it
$ cipi yml auto myapp on|off|status  # apply after every successful deploy
Command What it does
cipi yml generate Prints the app's configuration as it stands on the server — aliases, PHP version and per-app settings, extra databases, queue workers read back out of Supervisor, the scheduler and the backup profiles the app owns — as a ready-to-commit file.
cipi yml example A blank, fully commented template. Pass an app name and the placeholder databases and profiles land inside that app's namespace, so the template 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. Read this before you apply.
cipi yml apply Applies the plan. Add --yes for scripts and CI.
cipi yml auto Opt in — or out — of reconciling automatically after every successful deploy. status reports the current setting.

The file is looked up in current/cipi.yml, then current/cipi.yaml, then shared/cipi.yml — override with --file=<path>.

What the file can configure

app.aliases

Domain aliases

The declared list replaces the current one — an alias you delete from the file is removed from Nginx. Wildcards such as *.myapp.com are accepted, for multi-tenant subdomains. The primary domain is never managed here.

Aliases in the docs →
app.php · app.ini

PHP version and php.ini

Pin the app to PHP 8.3, 8.4 or 8.5 (already installed on the server) and set per-app overrides — upload_max_filesize, post_max_size, memory_limit and the rest. Server-wide values stay with cipi ini set.

php.ini in the docs →
databases

Extra databases

Beyond the one created with the app: reporting, analytics, whatever the release needs, on MariaDB or PostgreSQL. Credentials land in shared/cipi-databases.env and are never written back to the repository. Databases are created, never dropped.

Databases in the docs →
workers

Queue workers and Horizon

Declare each queue with its process count, tries and timeout, and Supervisor is reconciled to match. Or set horizon: true and let Horizon own the queues instead.

Workers in the docs →
schedule

The Laravel scheduler

One boolean turns * * * * * artisan schedule:run on or off for the app. No crontab editing, no forgotten entry on the next server.

Scheduler in the docs →
health

Healthcheck and rollback

An HTTP probe on one of the app's own domains, checked every five minutes and again right after every deploy. Optionally roll a failing release back automatically — the code symlink only, migrations are not undone.

Health checks in the docs →
backup.profiles

Backup profiles

Per-app profiles with their own scope, schedule, retention, destinations and encryption — a cheap database run every 30 minutes, a complete encrypted copy to S3 at night. Profile names are scoped to the app.

Backups in the docs →
reference

Every key, documented

The full schema, the lookup order, the validation rules and the notification hooks live in the deploy chapter of the documentation.

Read the reference →

A complete file

You do not have to write it by hand. cipi yml generate <app> prints what the server already has; commit that and cipi yml plan reports nothing to do.

bash
$ cipi yml generate myapp > cipi.yml   # then commit it
$ cipi yml plan myapp                  # reports nothing to do
yaml
version: 1

app:
  # 8.3, 8.4 or 8.5 — must already be installed (cipi php install 8.5)
  php: "8.5"

  # The declared list replaces the current aliases: one you remove here is
  # removed from the server. The primary domain is not managed here.
  aliases:
    - "www.myapp.com"
    - "*.myapp.com"      # wildcard, for multi-tenant subdomains

  # Per-app php.ini overrides. Server-wide values stay with `cipi ini set`.
  ini:
    upload_max_filesize: 50M
    post_max_size: 60M
    memory_limit: 512M

# Extra databases beyond the one created with the app. Credentials land in
# /home/myapp/shared/cipi-databases.env — never written back to the repo.
databases:
  - name: myapp_reporting
  - name: myapp_analytics
    engine: pgsql          # mariadb (default) or pgsql

workers:
  horizon: false           # true replaces the queue workers below
  queues:
    - queue: default
      processes: 2
    - queue: emails
      processes: 1
      tries: 5
      timeout: 300

# Laravel scheduler (* * * * * artisan schedule:run)
schedule: true

# HTTP healthcheck. Probed every 5 minutes and right after every deploy.
# The URL must be one of this app's own domains.
health:
  url: "https://myapp.com/up"
  expect: 200
  # grace: 8                      # seconds before the first probe after a deploy
  # postdeploy: false             # skip the check right after a deploy
  # rollback_on_unhealthy: true   # undo a release that fails the check
  #                               # (the code symlink only — migrations are NOT undone)

# Backup strategy for this app. Profile names must be myapp or myapp-*.
backup:
  profiles:
    # Frequent and cheap: databases only, without the noisy tables.
    - name: myapp-db
      scope: db
      databases: ["myapp", "myapp_*", "tenant_*"]
      exclude_tables: ["*.jobs", "*.telescope_*"]
      every: 30m           # 5m/10m/15m/20m/30m, 1h..12h, 1d..28d
      keep: 48             # keep the last 48 runs
      destinations: [local]

    # Slower, complete, off-site and encrypted.
    - name: myapp-nightly
      scope: all           # all | files | db
      cron: "0 2 * * *"
      keep_days: 14
      destinations: [s3]
      encrypt: true

Deploys ignore the file until you opt in

Nothing happens on deploy until you run cipi yml auto <app> on. With that 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, and a file that fails validation is reported by email (yml_fail) and never partially applied. A successful reconcile fires yml_apply.

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

Why it is safe to accept over Git

The file arrives from a repository, so anyone who can commit controls its contents. It 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.

Questions

Do I have to write the file by hand?

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

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 cipi yml auto on.

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.

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.

Full cipi.yml reference Install Cipi