Skip to content

Configuration

Everything on this page can be changed after the application exists, and most of it without redeploying.

The panel and the CLI set the same fields. Add application and Edit open the same form — the one shown in the overview — and this is what each of its fields is called on the command line:

Field in the panelFlagExplained under
Namethe positional argumentSet once, at creation; it names the unit and the socket
Published on--domainWhere it is published
Path--pathWhere it is published
Start commandeverything after --The start command
Internal port--portThe internal port
Health path--healthThe health path
Linked directories--link, --clear-linksLinked directories
Memory (MB), CPU (%), Max tasks--mem, --cpu, --tasksResource limits

Two differences worth knowing before you use the form. Name is absent when editing, because it cannot be changed. And Linked directories is a single field of space-separated names, where the CLI repeats --link; clearing the field is what --clear-links does.

The program and its arguments go after a -- separator, as a path relative to the release root:

Terminal window
corepanel app create <account-id> api --domain example.com --port 8080 -- ./server --config prod.toml

The -- is what keeps your application’s flags from competing with the CLI’s, so --config above belongs to your program. The working directory is the release root — the current symlink, not the release it was deployed from, so a rollback changes what runs without rewriting the unit.

FlagMeaning
--domain <fqdn>The domain or subdomain of the account. A domain alias is refused: it has no site file of its own, so the route belongs on the domain it points at
--path </sub>/ for the whole site, or a path under it. A sub-path mount strips the prefix before the request reaches your program

A sub-path mount works even when the site is a CMS whose .htaccess rewrites every unknown URL to a front controller: the application’s route is answered before that file is read, so /api reaches your program and the rest of the site keeps its permalinks. The reverse also holds — a deny rule or a password in that .htaccess does not protect the application, which authenticates its own callers. See .htaccess.

Both can be changed later with update; the route is moved for you.

Terminal window
corepanel app update <account-id> api --domain app.example.com --path /
Terminal window
corepanel app create <account-id> api --domain example.com --port 8080 -- ./server

Your program listens on this port inside its own network namespace, where nothing else on the server can reach it. CorePanel exports it as $PORT, which nearly every framework reads without configuration.

It must be 1024–65535: the process runs unprivileged and without CAP_NET_BIND_SERVICE, so a lower port could never be bound. Two applications may use the same number without conflicting — the namespaces are separate — but keeping them distinct makes a ps listing easier to read.

Terminal window
corepanel app create <account-id> api --domain example.com --port 8080 --health /healthz -- ./server

After every deploy and rollback, CorePanel connects to the endpoint and — if a health path is set — requests it. Without one, the check only proves that something accepted the connection, which means bind succeeded and nothing more. A process that binds and then fails every request would pass.

The path is the one your program sees, after the prefix strip: an application mounted at /api with --health /healthz is checked at /healthz, and the visitor-facing URL is https://example.com/api/healthz.

Make it cheap. It runs on every deploy and every two minutes afterwards (see the health check), so one that touches a database turns a slow database into a failed deploy and into a false alarm.

The Environment card on the application is an editable table. A plain value is there in the open; a secret shows as dots until you ask for it, with the eye button:

The Environment card as an editable table: LOG_LEVEL with its value in the clear, DATABASE_URL revealed after pressing the eye button, and STRIPE_SECRET_KEY still masked behind dots with buttons to replace it, reveal it, copy it and remove it

Edits accumulate and are saved together. The card counts what is unsaved and offers two ways out: Save and restart, which applies the change to the running process, and Save without restarting, which stores it for the next deploy. Editing row by row would mean restarting a live application once per variable, each restart landing on a configuration that is still half applied.

Turning Secret on hides the value from the listing. It does not make it unrecoverable: the eye button reveals it, and so does corepanel app env reveal. What it does is keep credentials off a screen somebody may be sharing, and make reading one a deliberate act — every revelation is recorded in the server log, with who asked and what for.

Whoever can edit a variable can also reveal it, and no password is asked for. That is deliberate rather than an oversight: the same person can replace the value and deploy code that prints it, so a prompt would buy friction, not secrecy. The record is what protects you, which is why there is one.

To change a secret you do not need to look at it: the pencil on a masked row opens an empty field for the new value and replaces the stored one when you save. Rotating a credential is the commonest thing done to a secret, and it should not put the old value on a screen — nor write an audit entry for a revelation nobody wanted. Opening that field and changing your mind counts as nothing: an empty box is not an empty value.

Turning Secret back off is only possible while the value is on screen — the switch stays locked on a masked row until you reveal it. The rule is that you cannot mark as plain a value you are not sending: the panel is never given a secret’s value, so a demotion without it would leave a variable listed in the clear that nobody in that request could actually see. Marking a plain value as secret needs no such thing; it only ever hides more.

Import .env takes a whole file at once. It shows what each line would do — replace an existing variable or add a new one — before anything is applied:

The Import a .env file dialog: a pasted file of four assignments, a toggle to mark credentials as secret, and a preview listing each name as "replaces" or "new" with STRIPE_SECRET_KEY and SESSION_SECRET marked Secret

Names that look like credentials — ending in KEY, TOKEN, SECRET, PASSWORD or DSN, plus DATABASE_URL — are marked secret for you. The toggle turns that off if you would rather decide yourself. Nothing reaches the server until you save.

The same, from corepanel:

Terminal window
corepanel app env list <account-id> api
corepanel app env reveal <account-id> api # everything, one audit entry
corepanel app env reveal <account-id> api STRIPE_SECRET_KEY
corepanel app env set <account-id> api LOG_LEVEL=debug
corepanel app env set <account-id> api DATABASE_PASSWORD='...' --secret
corepanel app env set <account-id> api NEXT_FLAG=on --no-restart
corepanel app env import <account-id> api ./.env # one restart, whatever its length
corepanel app env unset <account-id> api LOG_LEVEL

env import adds and overwrites; --replace also removes the variables the file does not mention, making it the whole environment. Both print how many changes were applied.

The variables live in a file systemd reads as root before dropping privileges, so your application receives them while the account cannot read the file that holds them — not over FTP and not from a shell. The application itself does receive them, which is the point, and is why the panel does not pretend a secret is beyond the reach of whoever deploys the code.

Values are stored encrypted in CorePanel’s database, under a key held per account outside it (/var/lib/corepanel/secrets/env, owner-only). AES-256-GCM, a fresh nonce per value, and the key is per account so one leaked copy exposes one account.

Be clear about what that buys, because the honest scope is narrower than “encrypted” usually suggests: it protects the database once it leaves the server — a backup, a snapshot, a copy taken for debugging, a bug that reads a file it should not. It does not protect against someone who already has the server, and it was never going to: CorePanel hands the values to systemd on every write and to you on every reveal.

The key never leaves the server, by design: a key travelling next to the data it protects is not a key, it is obfuscation. So an account backup carries the values decrypted and the restore re-encrypts them under the destination server’s own key — which is what lets an archive be restored onto any server without carrying a key across, and is also why the archive itself has to be handled like a password store.

Applications deployed before this shipped keep running: their values are read as they were stored and converted in the background the first time the upgraded panel starts. Nothing has to be re-entered.

A single value can be up to 16 KB, and the environment as a whole up to 256 KB. That second ceiling is not decoration: a process’s environment is capped by the kernel at around 2 MB, and past it the unit fails to start — which reads as a broken application rather than as a rejected variable.

Values cannot contain line breaks: the file is parsed in systemd’s EnvironmentFile syntax, where a newline ends the assignment. A multi-line credential — a PEM key, a formatted JSON — has to be minified or base64-encoded first.

VariableValue
PORTThe internal port you chose
CP_APP_NAMEThe application’s slug
CP_APP_DATAThe persistent data directory
CP_APP_TMPA private /tmp, invisible to the rest of the server

Those four names are reserved; your own variables cannot override them. The panel refuses them in the editor rather than after a round trip, and applies the same rule to the name itself: it starts with a letter or _ and contains only letters, digits and _.

An application that has been given a database also receives the CP_DB_* set and DATABASE_URL, on the same terms.

An application can be given a MySQL database of its own, from the Database panel on its page or from the command line:

Terminal window
corepanel app database create <account> api # schema + user + password
corepanel app database attach <account> api blog # one the account already has
corepanel app database detach <account> api # keeps the data

Creating it provisions the schema, a user dedicated to the application and a password, writes the connection into the environment and restarts the process. There is nothing to copy anywhere: the program reads it from its own environment.

The Database card of an application attached to the schema example_api, connecting as the user example_api, with a Detach button; below it the Environment table, where CP_DB_NAME, CP_DB_PASSWORD, CP_DB_HOST and CP_DB_SOCKET each carry a Managed badge and a read-only value, CP_DB_PASSWORD still masked behind an eye button and CP_DB_HOST shown empty

VariableValue
CP_DB_NAMEThe schema, <account_user>_<suffix>
CP_DB_USERThe user the application connects as, always @localhost
CP_DB_PASSWORDThe password, stored as a secret
CP_DB_SOCKETThe MariaDB unix socket to connect to
CP_DB_HOST, CP_DB_PORTPresent and empty — there is no TCP route
DATABASE_URLThe whole thing as a DSN, for a library that takes one

These are managed variables. The panel shows them read-only with a Managed badge — a connection that fails is debugged by looking at what the process was actually told, so they are shown rather than hidden — and app env set refuses to write one. Detaching is what removes them.

DATABASE_URL is the one exception: set it yourself and your value wins. Every driver spells a DSN a little differently, and a format CorePanel guessed wrong would be a variable you could not correct.

Attaching a schema the account already owns still creates a new user for the application. It cannot do anything else: CorePanel does not store MySQL passwords, so there is no existing credential to hand over, and resetting one would break every wp-config.php that shares it. The data is untouched and no other grant is changed.

Once the link exists it is protected in both directions. Deleting that database or that user — from the Databases section, from corepanel database or over the API — is refused, naming the application that holds it; changing the user’s password re-injects it and restarts the application, so a rotation never leaves the process with a dead credential. Deleting the application drops its user and never the schema — the files under its directory are the application’s, the database is the customer’s.

$CP_APP_DATA points at a directory that survives deploys, rollbacks and even deleting the application:

/var/opt/userapps/<account>/<app>/data

The program tree does not survive: every release replaces it. Anything your application must keep — uploads, a SQLite file, generated configuration — belongs under $CP_APP_DATA.

Some programs insist on writing inside their own tree and cannot be told otherwise. --link handles those: the named directory, relative to the release root, is replaced by a symlink into the data volume on every deploy.

Terminal window
corepanel app create <account-id> gitea --domain git.example.com --port 3000 \
--link data --link custom -- ./gitea web

./data then keeps its contents while everything around it is replaced. Repeat the flag per directory; corepanel app update ... --clear-links removes them all.

Every application has all three, and they are mandatory — an unbounded process on the same machine as the control panel is the straight road to an out-of-memory event that takes the panel down with it.

FlagDefaultMeaning
--mem <mb>256Hard memory ceiling. Exceeding it means the kernel kills the process; systemd restarts it
--cpu <pct>50Percentage of one core. 100 is a whole core, 200 is two
--tasks <n>64Threads and processes together
Terminal window
corepanel app update <account-id> api --mem 1024 --cpu 200

They are applied as cgroup properties, so they are enforced by the kernel rather than requested politely. If the application stops serving under the new settings, the previous ones are restored.

On Personal the three are fixed at those defaults. Applications run — that is not the gated part — but giving one more of the machine is a Pro feature, so the panel shows the fields disabled and the CLI refuses a higher value with the reason instead of quietly applying 256 MB to a form that asked for 2 GB. Lowering a limit never needs a license, and an application that was already configured under a paid edition keeps the limits it has if the license lapses: a payment problem must never shrink a running application.

Sizing, roughly: a small Go or Rust API is comfortable at the defaults; a service with an embedded database, background jobs and a template cache wants 512–1024 MB and a full core; a program that spawns workers needs --tasks raised or it will fail to fork under load.

In the panel, Edit on the application opens the creation form again with the current values, minus the name. Only the fields you actually change are sent, so saving does not re-apply — or re-validate — settings nobody touched; if nothing changed, the form says so instead of writing.

Terminal window
corepanel app update <account-id> api --mem 512 --health /healthz -- ./server --addr :8080

Omitted fields are left as they are. The change is applied to the release already running — no redeploy — and if the application stops serving under the new settings, the previous ones are put back. An application that was stopped stays stopped.

Each account has a maximum number of applications, in parallel with its subdomain and addon-domain limits, and hosting packages carry it to the accounts they provision.

Zero means none here, not unlimited — this is the one limit in CorePanel that reads the other way round, and it has no unlimited value at all. An application is a resident program with its own memory, socket and network namespace, so an account gets to run them only when somebody grants a number: on the account (Edit account → Applications, or corepanel account update <id> --max-apps N) or on its hosting package.

Lowering the number below the applications already running is refused, exactly like any other limit below current usage; the applications themselves are never deleted to make room.

An application has to pass two allowances, and they answer different questions:

Who sets itZero meansCounted over
Per-account (--max-apps)you, per account or per packagenonethat account
Edition capthe licenseunlimitedthe whole server
EditionApplications
Personal2 on the server, in any mix of accounts
Pro, Businessunlimited

The two refusals are deliberately different messages, because your next step is different: the account one is raised by editing the account or its hosting package, the edition one by upgrading the server. The cap counts every application on the box, not per account — counting per account would be answered by creating another account.

A restore is exempt. An account that comes back with more applications than the edition allows comes back complete, with a warning on the restore report; dropping one to respect a commercial limit would destroy the data directory, which is the one thing a redeploy can never rebuild. Applications restored over the cap on Personal also come back on the fixed resource limits, and the report says so per application.

The complete list, with every default, lives in the CLI reference.