cipi app create

cipi app create supports two app types: Laravel (default) and Custom (--custom). Laravel apps get a fully isolated environment: Linux user, PHP-FPM pool or Laravel Octane (FrankenPHP) since v5.0, Nginx vhost, database (MariaDB by default; optional PostgreSQL since v4.8.0), Supervisor worker, crontab entry, Deployer zero-downtime releases, SSH deploy key, and auto-compiled .env. Custom apps are simpler — see custom apps for details.

Laravel app (default — PHP-FPM)

bash
$ cipi app create

Laravel Octane (FrankenPHP)

bash
$ cipi app create --octane
$ cipi app create --octane=frankenphp   # explicit (same as --octane)

See Laravel Octane for requirements, convert, and how the vhost differs from FPM.

Non-interactive (flags)

bash
$ cipi app create \
    --user=myapp \
    --domain=myapp.com \
    --repository=git@github.com:you/myapp.git \
    --branch=main \
    --php=8.5

# Laravel Octane (FrankenPHP) — v5.0+
$ cipi app create --user=myapp --domain=myapp.com \
    --repository=git@github.com:you/myapp.git --octane

# optional PostgreSQL (v4.8.0+, after cipi db install pgsql)
$ cipi app create --user=myapp --domain=myapp.com \
    --repository=git@github.com:you/myapp.git --engine=pgsql
--userLinux username for the app. Must be unique, lowercase, alphanumeric.
--domainPrimary domain (e.g. myapp.com). Used for Nginx vhost and APP_URL.
--repositorySSH Git URL (e.g. git@github.com:you/repo.git). Must be SSH, not HTTPS.
--branchBranch to deploy. Defaults to main.
--phpPHP version for this app (e.g. 8.5). Defaults to PHP 8.5. Must be an installed version — only 8.3, 8.4 and 8.5 are accepted since v4.5.4 (Deployer 8 requires PHP ≥ 8.3).
--octane / --octane=frankenphpServe HTTP via Laravel Octane (FrankenPHP) instead of PHP-FPM (since v5.0). Laravel apps only — rejected with --custom. See Laravel Octane.
--engineDatabase engine: mariadb (default) or pgsql (since v4.8.0; requires cipi db install pgsql). Interactive create prompts when PostgreSQL is installed. Writes matching .env / connection URL.
If a GitHub or GitLab token is configured, Cipi automatically adds the SSH deploy key and creates the webhook on the repository — no manual steps needed. See Git auto-setup for setup instructions and fallback options when auto-configuration is not available.

Laravel Octane (FrankenPHP)

Since v5.0, Laravel apps can serve HTTP via Laravel Octane with the FrankenPHP server instead of a per-app PHP-FPM pool. Octane and classic FPM apps run side by side on the same Cipi server.

What Cipi configures

  • Allocates a localhost port in 8100–8999 and stores octane / octane_port in apps.json
  • Nginx vhost uses proxy_pass to Octane and serves static files from current/publicno per-app FPM pool
  • Supervisor program ${app}-octane alongside queue workers
  • Deployer template laravel-octane.php restarts / reloads Octane on deploy
  • .env: OCTANE_SERVER=frankenphp, OCTANE_HTTPS=true

App requirements

Your Laravel repository must include laravel/octane and run php artisan octane:install --server=frankenphp. Octane starts after the first successful deploy.

bash
# create an Octane app
$ cipi app create --user=myapp --domain=myapp.com \
    --repository=git@github.com:you/myapp.git --octane

# convert an existing FPM app → Octane (or the reverse)
$ cipi app convert myapp --to=octane
$ cipi app convert myapp --to=fpm

# tune Octane workers (also see app limits)
$ cipi app limits myapp --octane-workers=4

cipi app convert --to=octane|fpm rewrites the pool / vhost / Supervisor / Deployer template / .env, and re-applies SSL when a certificate is already present.

Octane tip: keep long-running queue work on cipi worker or Horizon — Octane handles HTTP only. Pair with Reverb for WebSockets.

cipi app create --custom

Creates a custom app with classic deploy (no zero-downtime): code is deployed into htdocs — no current/shared symlinks. Ideal for static sites, SPAs (Vue, React, Svelte), WordPress, other CMS, or any non-Laravel PHP project or framework.

During creation you only choose the document root (default /, or e.g. www, dist, public). Nginx is pre-configured with index index.html index.php, try_files $uri $uri/ /index.php?$args, and error_page 404 /404.html — no prompts for try_files or entry point.

Git optional (SFTP-only)

The Git repository is optional for custom apps. Laravel apps still require a repository. If you skip the repository for a custom app, Cipi creates /home/<app>/htdocs with a placeholder index.html and does not configure a deploy key or webhook — you upload files with SFTP (or SCP/rsync) to ~/htdocs as the app user. The onboarding output explains this “no repo — SFTP only” workflow. If you do provide a repository, behavior is unchanged: use cipi deploy <app> to pull code into htdocs.

What’s included and what you add

Custom apps have no database, no .env, no cron, and no queue workers. When a repository is configured, a deploy key and (with Git auto-setup) webhook are shown; for SFTP-only apps, those are omitted. The post-creation summary lists SSH access and next steps accordingly.

If your custom app needs a database (e.g. WordPress, Drupal), create one with cipi db create --name=<app> after deployment. See cipi db for backup, restore, and password management.

With Git — non-interactive example:

bash
$ cipi app create --custom --user=mysite --domain=mysite.com \
    --repository=git@github.com:you/mysite.git --docroot=dist

SFTP-only — omit --repository and --branch:

bash
$ cipi app create --custom --user=mysite --domain=mysite.com --docroot=dist

With a repository, use cipi deploy <app> to deploy; code is cloned into /home/<app>/htdocs.

app list / app show / app edit / app delete

Command Description
cipi app list List all apps with domain, PHP version, and status ((suspended) when offline)
cipi app show <app> Full details: domain, PHP, deploy key, workers, webhook, suspend state. For custom apps: type "Custom", docroot; webhook (and deploy key) omitted when SFTP-only without a repository.
cipi app edit <app> --php=8.5 Hot-swap PHP version. Updates FPM pool, Nginx socket, Supervisor, crontab, Deployer config, and .env — zero downtime
cipi app edit <app> --branch=develop Change the deploy branch
cipi app edit <app> --domain=new.example.com Rename the primary domain (since v4.6.2). Validates format and uniqueness, moves the old primary to aliases, regenerates the Nginx vhost, updates APP_URL, refreshes Git webhooks when auto-configured, and re-issues Let's Encrypt when a certificate already existed
cipi app edit <app> --repository=<SSH-URL> Attach or change the Git repository (e.g. enable deploy on a custom SFTP-only app created without --repository). Composable with --branch, --php, and --domain
cipi app edit <app> --node-build='npm ci && npm run build' Run a Node build after Composer vendors on every deploy (since v5.0). Deployer executes .deployer/node-build.sh (fail-closed; command is validated). Clear with --no-node-build
cipi app edit <app> --predeploy-snapshot Enable a database dump before every deploy (since v5.0). Same behaviour as passing --snapshot on each cipi deploy. See Pre-deploy DB snapshots
cipi app convert <app> --to=octane|fpm Convert between PHP-FPM and Laravel Octane (since v5.0). See Laravel Octane
cipi app env <app> [--show|--get|--set|--unset] Open the app's .env in nano, or manage keys non-interactively since v5.0.3 (--show/--get/--set/--unset, optional --json). Exits with error for custom apps (no .env).
cipi app run <app> <cmd> [args…] Whitelisted non-interactive command as the app user (since v5.0.3). See app run
cipi app deploy-config <app> Structured Deployer recipe options (since v5.0.3). See deploy-config
cipi app webhook recreate <app> [--rotate-secret] Recreate the GitHub/GitLab deploy webhook; optional secret rotation updates CIPI_WEBHOOK_TOKEN (since v5.0.6). REST: POST /api/apps/{name}/webhook/recreate (API 1.15.0+). Since 5.0.6, app edit --repository= only recreates webhook/deploy key when the repository URL actually changes.
cipi app fix-permissions [app] Reapply the permission layout Cipi created the home with (since v5.2.1). One named app, or every app. See app fix-permissions
cipi app reset-password <app> Regenerate the app's Linux user SSH password. The new password is displayed on screen — save it immediately
cipi app reset-db-password <app> Regenerate the app's database password (MariaDB or PostgreSQL per app engine) and automatically update DB_PASSWORD in the app's .env. Exits with error for custom apps (no database).
cipi app delete <app> Permanently remove the app, user, database (if Laravel), Nginx vhost, FPM pool, and Supervisor workers. For custom apps, skips database drop (none was created). Since v5.2.2, also revokes the app's Meilisearch key and drops its indexes — see cipi search. Asks for confirmation.
cipi app delete <app> --force Same as delete but skips the confirmation prompt — for scripts, the panel API, and cipi-cli

cipi app fix-permissions

The panel already had cipi api fix-permissions and cipi gui fix-permissions; apps did not. A blunt chown -R app:app /home/<app> is wrong: nginx writes vhost logs as www-data into logs/ (2775 setgid), the home must stay 750 so www-data (in the app group) can read the docroot, .ssh must be 700 or OpenSSH StrictModes refuses the deploy key, and shared/.env is 640. This command reapplies that layout — ownership of the tree except logs/, then the log/ACL helper, storage dirs 775, Composer auth.json 640, cipi-databases.env 600. One named app, or every app.

bash
$ cipi app fix-permissions myapp
$ cipi app fix-permissions          # every app on this server

cipi deploy already runs the log-ACL helper before Deployer. fix-permissions is the full home, on demand, after someone unpacked a zip as root or chmod'd .ssh to 775. Available since v5.2.1. No migration: cipi self-update copies the libs and refreshes shell completion. The panel sudoers file lists the new command so a future GUI can call it; existing servers pick that line up the next time sudoers is rewritten (the CLI runs as root and does not need it).

cipi app reverb

Laravel Reverb is Laravel's first-party WebSocket server. Cipi has run it since v5.0; v5.1.2 is the release that made it work in production — generated credentials, a scheme derived from the certificate, raised connection limits, and a proxy rule that no longer swallows your own routes.

Enabling Reverb on a Laravel app wires four things:

127.0.0.1:9000–9099A free localhost port, allocated per app and recorded in apps.json. Reverb never listens on a public interface.
${app}-reverbA Supervisor program running artisan reverb:start as the app user, logging to /home/<app>/logs/reverb.log.
nginxA proxy block on the app's own domain for /app/{key} (the WebSocket handshake) and /apps/{id}/… (the HTTP API the application broadcasts through).
.envREVERB_* and VITE_REVERB_*, generated on first enable and never rotated afterwards.
bash
$ cipi app reverb enable myapp
$ cipi app reverb enable myapp --restart-supervisor   # also apply the new open-file limit now
$ cipi app reverb status myapp
$ cipi app reverb disable myapp
--restart-supervisorRestart Supervisor as part of enable, so the raised file-descriptor limit applies immediately. This restarts every queue worker on the server, which is why Cipi never does it on its own.

Reverb is Laravel onlyenable refuses a --custom app. Running enable again on an app that already has Reverb is safe and useful: it does not reallocate the port or touch the credentials, it re-derives the public host and scheme and fills in any .env key that went missing.

Enabling it end to end

bash
# 1. In the project, on your machine — require the package and the client side,
#    then commit. `install:broadcasting` writes resources/js/echo.js and config/reverb.php.
$ composer require laravel/reverb
$ php artisan install:broadcasting

# 2. On the server — allocate the port, the Supervisor program and the nginx proxy.
$ cipi app reverb enable myapp

# 3. A certificate, so clients get wss:// instead of ws:// (skip if the app already has one).
$ cipi ssl install myapp

# 4. Deploy: composer install, the Node build, then Reverb starts on the new release.
$ cipi deploy myapp
$ cipi app reverb status myapp

Order matters only in that the Node build has to run after reverb enable: Vite bakes VITE_REVERB_* into the bundle at build time. Cipi's node_build step runs after Deployer has linked the shared .env, so a deploy that follows enable produces a frontend that can find the socket. Change the domain or install a certificate later and you need another deploy for the bundle to catch up.

What Cipi writes in .env

env
# Laravel Reverb — managed by Cipi (cipi app reverb status myapp)
# Where the Supervisor program listens. Cipi owns this pair outright.
REVERB_SERVER_HOST=127.0.0.1
REVERB_SERVER_PORT=9000

BROADCAST_CONNECTION=reverb

# Generated once on the first enable, then never touched again.
REVERB_APP_ID=482913
REVERB_APP_KEY=3f9c1ab27de40518b6c4
REVERB_APP_SECRET=a71f0e5c8d24b93a6f10c7e5482db9af

# The public side — derived from what nginx actually serves.
REVERB_HOST=myapp.com
REVERB_PORT=443
REVERB_SCHEME=https

# Read by Vite at build time and baked into the frontend bundle.
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

Before 5.1.2 the three credentials were left blank for you to invent — and an empty REVERB_APP_KEY is exactly what stops reverb:start from booting. They are now generated on the first enable and an existing non-empty value always wins, so an app that arrived with its own credentials keeps them. Cipi never rotates them: a new key would disconnect every live client and invalidate any frontend bundle already built against the old one.

Connecting from the frontend

The stock resources/js/echo.js from install:broadcasting already reads exactly the variables Cipi writes — nothing to change:

js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
    broadcaster: 'reverb',
    key: import.meta.env.VITE_REVERB_APP_KEY,
    wsHost: import.meta.env.VITE_REVERB_HOST,
    wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
    wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
    forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
});

Because nginx fronts Reverb on the app's own domain and port, there is no extra port to open in the firewall and no second certificate to manage. cipi app reverb status prints the exact URL a client ends up dialling.

/app and /apps belong to the protocol. Up to 5.1.1 the proxy was written as location /app, which in nginx is a prefix match, not a path — so on every app with Reverb enabled, /appointments, /apple and /application/… were proxied into Reverb instead of reaching Laravel. Since 5.1.2 the block is the regex ~ ^/apps?(/|$), which matches only /app/{key} and /apps/{id}/…. An app that has its own /app or /apps route still cannot use Reverb behind the same domain — put it on a dedicated subdomain instead.

ws:// or wss://

Cipi derives the public host, port and scheme from what the server actually serves rather than assuming HTTPS:

no certificatehttp on port 80 — the app is served over HTTP, so the socket is ws://.
certificate presenthttps on port 443 — wss://.
wildcard domain*.example.com resolves to a concrete host (www.example.com), because no browser can open a socket to a wildcard. A multi-tenant frontend has to pick its own host at runtime.

Installing a certificate later — HTTP-01 or DNS-01 — moves the app's Reverb settings to wss:// on the spot, and a domain change re-derives them too. Before 5.1.2 these values were hardcoded to https/443 at enable time, which on an app without a certificate meant a browser silently refusing the connection as mixed content, with nothing in the Reverb log to explain it. reverb status now warns when the app has a certificate but the .env still says ws://.

Concurrent connections and open files

Every open WebSocket costs one file descriptor inside Reverb and two connections inside nginx, and the stock soft limit of 1024 stops an app at roughly a thousand concurrent clients — with nothing in any log to say why. Since 5.1.2 Cipi raises both ceilings:

SupervisorA systemd drop-in at /etc/systemd/system/supervisor.service.d/cipi-nofile.conf setting LimitNOFILE=65535, inherited by every program Supervisor runs.
nginxworker_rlimit_nofile 65535 and worker_connections 8192 in nginx.conf, applied on a reload.

nginx picks its new limits up on the reload Cipi performs. Supervisor picks its own up only on a restart, which also restarts every queue worker on the server — so Cipi configures the drop-in, tells you it is pending, and leaves the decision to you:

bash
$ systemctl restart supervisor
# or, in one go, at enable time:
$ cipi app reverb enable myapp --restart-supervisor

The proxy read timeout was another silent limit: it used to be exactly Reverb's 60-second ping interval, so every idle client was a coin flip between the ping and the timeout. It now sits well clear of it, and Reverb's own ping/prune cycle is what reaps dead connections.

cipi app reverb status

Reports the port, what Supervisor is doing with the process, the URL a client should be dialling, and the three failure modes that leave nothing in any log:

bash
$ cipi app reverb status myapp
  Reverb: enabled 127.0.0.1:9000
myapp-reverb                     RUNNING   pid 21847, uptime 3 days, 4:12:07
  Clients dial: wss://myapp.com:443/app/3f9c1ab27de40518b6c4
  Open files: 65535 (supervisor)
Missing in .env: …A credential is absent or empty — reverb:start will not boot. Re-run cipi app reverb enable.
certificate but REVERB_SCHEME is 'http'The app gained a certificate after Reverb was enabled. Re-run enable to move it to wss://, then deploy so the bundle catches up.
default open-file limitThe drop-in is in place but Supervisor has not been restarted, so the process is still capped near a thousand clients.

The process log is /home/<app>/logs/reverb.log, and supervisorctl status myapp-reverb / restart myapp-reverb control the program directly.

Disabling

cipi app reverb disable stops and removes the Supervisor program, regenerates the vhost without the proxy block, re-applies SSL, and — since 5.1.2 — points BROADCAST_CONNECTION back at whatever the app was broadcasting through before Reverb took over, falling back to log. Previously it left BROADCAST_CONNECTION=reverb behind, so every broadcast failed against a server that was no longer running.

Only REVERB_SERVER_HOST and REVERB_SERVER_PORT are removed from the .env. The credentials stay: they are what a re-enable — or a frontend bundle already built against them — depends on.

Cloning and cipi.yml

cipi app clone used to copy the source's REVERB_* keys into the clone's .env — pointing a staging frontend at the production socket and handing it the production app secret — while never starting a Reverb for it. Since 5.1.2 a cloned Reverb app gets its own port, its own credentials and its own domain, the way it already got its own Octane.

Reverb can also be declared in the project file with workers.reverb: true|false, planned and applied by cipi yml apply like Horizon and the queue workers, and emitted by cipi yml generate. Declaring it on a --custom app is refused up front.

yaml
workers:
  horizon: false
  reverb: true             # adds a Reverb WebSocket server to this app
Upgrading from 5.0.x or 5.1.1? cipi self-update runs migration 5.1.2, which raises both limits (and reports whether Supervisor still needs a restart), rewrites the vhost of every app whose Reverb location is still the greedy /app prefix — backed up to /var/lib/cipi/vhost-backup-5.1.2, tested with nginx -t, SSL re-applied, and rolled back as a batch on failure — and fills in the missing .env keys without overwriting anything already there, so an app whose socket settings were tuned by hand keeps them. Re-deploy your Reverb apps afterwards so the frontend bundle picks up the VITE_REVERB_* values.

cipi app clone

Since v5.0, clone an existing Laravel app into a new staging (or review) app with its own domain. Sets cloned_from in apps.json; does not copy webhook or git IDs. If the source has Reverb, the clone gets its own port, credentials and public host (since v5.1.2) — it no longer inherits the production socket. See cipi app reverb. Since v5.2.2, a cloned Scout app gets its own Meilisearch key and SCOUT_PREFIX; the source's MEILISEARCH_* keys are excluded from the copied .env, because a clone that inherited them would reindex into production. See cipi search.

bash
$ cipi app clone myapp --domain=staging.myapp.com
$ cipi app clone myapp --domain=staging.myapp.com --name=myapp-stg --branch=develop --with-db
$ cipi app clone myapp --domain=staging.myapp.com --no-db
--domainRequired. Primary domain for the new app.
--nameOptional Linux username for the clone.
--branchDeploy branch for the clone (defaults to the source app's branch).
--with-dbCreate a fresh database for the clone.
--no-dbSkip database provisioning for the clone.

cipi app limits

Since v5.0, set per-app resource limits with hard caps enforced by Cipi.

bash
$ cipi app limits myapp --fpm-max-children=20 --memory-limit=256M
$ cipi app limits myapp --octane-workers=4 --worker-procs=3
$ cipi app limits myapp   # show current limits
--fpm-max-childrenPHP-FPM pm.max_children for FPM apps.
--memory-limitPHP memory_limit.
--octane-workersOctane worker count for FrankenPHP apps.
--worker-procsSupervisor queue worker process count.

app suspend / app unsuspend

Available since v4.5.8. Take an app offline without deleting it — useful for billing holds, maintenance windows, or staging sites you want fully dark. Suspending swaps the Nginx vhost for a static HTTP 503 page served from /var/www/cipi-suspended/.

bash
$ cipi app suspend myapp      # take offline (503 page)
$ cipi app unsuspend myapp    # restore normal vhost
Command Description
cipi app suspend <app> Sets suspended: true in apps.json, rebuilds the vhost to return 503 for all requests. Idempotent if already suspended.
cipi app unsuspend <app> Clears the flag, restores the normal Laravel/custom vhost, and reapplies SSL blocks. Idempotent if already online.

Behaviour

  • HTTPS included — certbot clones the suspension vhost into the :443 block, so HTTPS also shows the offline page
  • Let's Encrypt still works — the /.well-known/acme-challenge/ path stays public so certificates can be issued or renewed while suspended
  • Survives vhost regeneration — alias changes, PHP edits, and SSL installs respect the suspended flag
  • Visible in listingscipi app list marks suspended apps; cipi domains appends ⏸ suspended on each row
Suspend vs basic auth: cipi basicauth shows a login prompt but still runs your app. app suspend stops PHP entirely and serves a static offline page — visitors never reach Laravel. Use suspend for “site closed”; basic auth for “invite-only preview”.

Also available via the REST API (POST /api/apps/{name}/suspend), cipi-cli (apps suspend), and the WHMCS module (Suspend / Unsuspend buttons). Requires token ability apps-suspend for API access.

cipi basicauth

Available since v4.5.2. Protect any app — Laravel or custom — behind an Nginx username/password prompt. Useful for staging sites, internal tools, or apps that aren't ready for public traffic yet.

Command Description
cipi basicauth enable <app> [--user=NAME] [--password=PASS] Turn on HTTP basic auth. Credentials are generated when omitted and shown once on screen — save them immediately
cipi basicauth disable <app> Remove the prompt and clear stored credentials
cipi basicauth status <app> Show whether basic auth is enabled and the configured user

Credentials are hashed with openssl passwd -apr1 (no apache2-utils needed) and stored in /etc/nginx/cipi-basicauth/<app>.htpasswd; the enabled state lives in apps.json. The auth_basic directives are injected per location block, so protection survives vhost regeneration (alias changes, PHP edits) and is cloned into the :443 block by certbot — HTTPS is covered too. ACME challenges stay public, so certificate issuance and renewal are never blocked. Basic auth is removed automatically on cipi app delete.

This is distinct from cipi auth, which manages the Composer auth.json for private package repositories.

Managing ENV variables

Every Laravel app has a single .env file living at /home/<app>/shared/.env. Custom apps have no .env. It is created and pre-populated by Cipi during app create with the database credentials, APP_KEY, APP_URL, cache/session/queue settings, and the webhook token. The shared/ directory is symlinked into every release, so the same .env is always active regardless of which release is current.

Edit interactively via CLI

The safest way to change ENV values is through Cipi itself — it opens the file in nano as the app user, with the correct permissions:

bash
$ cipi app env myapp

Save with Ctrl+O then exit with Ctrl+X. Changes take effect immediately for new requests — no restart needed for most values. If you change queue connection or cache driver, restart the workers:

bash
$ cipi worker restart myapp

Non-interactive flags (v5.0.3+)

Since v5.0.3, scripts, the panel API, and the web GUI can manage .env without opening an editor:

bash
$ cipi app env myapp --show
$ cipi app env myapp --show --json
$ cipi app env myapp --get=APP_URL
$ cipi app env myapp --set=APP_DEBUG=false
$ cipi app env myapp --unset=LEGACY_KEY

REST: GET|PUT /api/apps/{name}/env (ability apps-env, API 1.14+). MCP: AppEnvShow, AppEnvUpdate.

Edit directly via SSH

You can also edit the file directly over SSH as root or as the app user:

bash
# as root
$ nano /home/myapp/shared/.env

# or switch to the app user first
$ su - myapp
$ nano ~/shared/.env

Key ENV variables set by Cipi

Variable Description Set by
APP_KEY Laravel encryption key — generated once at app creation Cipi
APP_URL Updated automatically by cipi ssl install Cipi
DB_CONNECTION mysql for MariaDB (drop-in compatible), or pgsql when the app engine is PostgreSQL (v4.8.0+) Cipi
DB_DATABASE / DB_USERNAME / DB_PASSWORD Auto-generated credentials for the app's isolated database Cipi
CACHE_STORE database — uses the app's database Cipi
SESSION_DRIVER database Cipi
QUEUE_CONNECTION database Cipi
BROADCAST_CONNECTION Set to reverb on enable; restored to the previous driver (or log) on disable (v5.1.2+) Cipi
REVERB_APP_ID / KEY / SECRET Generated once on the first cipi app reverb enable, then never rotated. An existing non-empty value always wins. Cipi (v5.1.2+)
REVERB_HOST / PORT / SCHEME Public socket: derived from the certificate (https/443 or http/80). A wildcard domain resolves to a concrete host. Cipi (v5.1.2+)
VITE_REVERB_APP_KEY / HOST / PORT / SCHEME Copies of the public Reverb values, baked into the frontend by Vite at build time. Redeploy after enable, a domain change or SSL. Cipi (v5.1.2+)
REVERB_SERVER_HOST / PORT Localhost bind for the Supervisor program. Removed on disable; the credentials stay. Cipi
CIPI_WEBHOOK_TOKEN HMAC secret for cipi-agent webhook validation Cipi
CIPI_APP_USER Linux username owning this app Cipi
CIPI_MCP Enable or disable the built-in MCP server at /cipi/mcp User (true by default)
Do not change DB credentials manually. If you need to regenerate the database password use cipi db password myapp (or cipi app reset-db-password myapp) — it updates both the engine and the .env atomically. Editing them by hand risks leaving the two out of sync.

Adding your own variables

Add any custom variable at the bottom of the file as you normally would in a Laravel project. They are preserved across deploys because the .env lives in shared/ and is never overwritten by Deployer.

env
# your custom variables
STRIPE_KEY=sk_live_...
STRIPE_SECRET=sk_live_...
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailgun.org

cipi app run

Since v5.0.3, run a whitelisted non-interactive command as the app user — useful from the CLI, panel API, and web GUI “App commands”. Editors, pagers, shells, and REPLs are blocked (nano, vim, less, bash, tinker, …). Interactive flags (e.g. tail -f, php -a) are rejected.

bash
$ cipi app run myapp composer install --no-dev
$ cipi app run myapp npm ci
$ cipi app run myapp ls -la shared
$ cipi app run --commands          # list allowed binaries
$ cipi app run --commands --json

Allowed binaries include composer, npm/npx/yarn/pnpm, ls/ll, cat/head/tail, filesystem helpers, archives, git, php, node, and find. REST: POST /api/apps/{name}/run (async job, ability apps-run) and GET /api/run-commands (API 1.14+). MCP: AppRun, AppRunCommands.

v5.0.4+ fixed /usr/bin/env: '--': No such file or directory — every whitelisted command (including GUI App commands) failed before that patch. Run cipi self-update.

cipi app logs

Tail application logs in real-time. Logs are rotated daily and kept for 14 days. By default, all logs are shown including Laravel daily logs (laravel-YYYY-MM-DD.log) from shared/storage/logs/.

bash
$ cipi app logs myapp                  # all logs (incl. Laravel daily logs)
$ cipi app logs myapp --type=nginx     # Nginx access + error
$ cipi app logs myapp --type=php       # PHP-FPM errors
$ cipi app logs myapp --type=worker    # queue worker output
$ cipi app logs myapp --type=deploy    # deploy history
$ cipi app logs myapp --type=laravel   # Laravel application logs
--type=nginxNginx access and error logs
--type=phpPHP-FPM error log
--type=workerSupervisor / queue worker output
--type=deployDeployer output — full deploy history with timestamps
--type=laravelLaravel application logs from shared/storage/logs/ (daily rotating laravel-YYYY-MM-DD.log)

app artisan & app tinker

Run Artisan commands and Tinker as the app user with the correct PHP version and open_basedir context — exactly as they would run during a deploy.

bash
$ cipi app artisan myapp migrate:status
$ cipi app artisan myapp queue:retry all
$ cipi app artisan myapp db:seed --class=ProductionSeeder
$ cipi app artisan myapp cache:clear
$ cipi app tinker myapp

SSH as the app user

Each app runs under its own isolated Linux user. Sometimes you need to work directly inside that user's environment — inspect files, run one-off scripts, or debug something that only reproduces as the correct user.

Direct SSH as app user (recommended)

App users can SSH directly to the server with the password generated at app creation:

bash
# connect as the app user (password auth)
$ ssh myapp@your-server-ip

# you are directly inside the app user's shell
myapp@server:~$ pwd
/home/myapp

myapp@server:~$ cd ~/current
myapp@server:~$ ls

The password is shown when the app is created (or use cipi app reset-password myapp to regenerate it). This works for SFTP clients, IDE remote sessions, and terminal access.

Via cipi (admin path)

If you are already connected as cipi, you can switch directly to any app user:

bash
$ ssh cipi@your-server-ip
cipi@server:~$ sudo su - myapp

myapp@server:~$ pwd
/home/myapp

Reset the app user password

If you need to regenerate an app user's password (e.g. for direct SSH or SFTP), use:

bash
$ cipi app reset-password myapp

The new password is displayed on screen — save it immediately.

Useful commands once logged in as the app user

bash
# navigate to the active release
myapp@server:~$ cd ~/current

# run artisan directly with the correct PHP version
myapp@server:~$ /usr/bin/php8.5 ~/current/artisan tinker

# inspect the shared .env
myapp@server:~$ cat ~/shared/.env

# tail all logs
myapp@server:~$ tail -f ~/logs/*.log

# check active releases (ll is a built-in alias for ls -al)
myapp@server:~$ ll ~/releases/

Every app user's .bashrc defines a handy ll='ls -al' alias (since v4.5.5) for quicker directory listings over SSH, alongside the deploy and composer shortcuts. Apps created before 4.5.5 receive the ll alias automatically on the next cipi self-update via the 4.5.5 migration.

The app user's open_basedir restriction limits PHP to /home/myapp. This is enforced at the PHP-FPM level, not at the shell level — you can access any file your shell user can read when working in the terminal.