Skip to content

Use cases

An application is always the same machinery; what changes is where it is published and what it owns. This page walks the shapes that come up in practice and gives the exact configuration for each.

Two settings do most of the work:

  • The target — which domain or subdomain the application is published under.
  • The mount path/ for the whole site, or /something for one path under it.

A sub-path mount strips the prefix: an application mounted at /api receives /orders when a visitor requests /api/orders, so the same build works whether you mount it at the root or under a path.

Each shape below is written as a create command, because that is the compact way to show a whole specification at once. In the panel the same values go into the Add application form, field by field — which field is which flag is a single table.


The classic case: a PHP or static site keeps serving, and one path goes to a program.

Terminal window
corepanel app create <account-id> api --domain example.com --path /api --port 8080 \
--health /healthz -- ./server
https://example.com/the docroot, as before
https://example.com/api/ordersyour program, which sees /orders

Nothing about the site changes: the document root, its PHP version, its certificates and its WAF policy stay exactly as they were. Removing the application later puts /api back to being served from the docroot.

Watch out for: a real directory named api inside the docroot. The application wins, and the files become unreachable while it exists.


The cleanest shape when the program is the site — a dashboard, an internal tool, an API with its own hostname.

Terminal window
corepanel subdomain add <account-id> app example.com
corepanel app create <account-id> app --domain app.example.com --path / --port 3000 \
--health /healthz -- ./server

The subdomain gets its own certificate automatically. Because the mount is /, every path — including /favicon.ico and any 404 — is your program’s responsibility; there is no docroot fallback behind it.

Watch out for: deploying for the first time before pointing anyone at the hostname. Until the first successful deploy the application is not published, and that is deliberate — publishing earlier would 502 the whole site until the artifact arrived.


Static build served by the web server, API calls handled by the program. This is faster and cheaper than routing everything through the application.

Terminal window
# The SPA build lives in the docroot; unknown paths fall back to index.html.
# The web mode is chosen when the domain or subdomain is created…
corepanel subdomain add <account-id> app example.com --web-mode spa
# …and the backend takes one path under it
corepanel app create <account-id> api --domain app.example.com --path /api --port 8080 \
--health /healthz -- ./server

On a domain that already exists, change the web mode from the panel — the domain’s Web settings — since the CLI only takes --web-mode at creation time.

https://app.example.com/assets/app.jsthe docroot
https://app.example.com/dashboardindex.html, so client-side routing works
https://app.example.com/api/meyour program, which sees /me

The spa web mode is what makes a client-side route like /dashboard return the app instead of a 404. The application’s path is matched first, so API calls never fall into that fallback.


A Git server, a metrics dashboard, a bookmarking tool — something that writes files and must keep them across upgrades.

Terminal window
corepanel app create <account-id> gitea --domain git.example.com --path / --port 3000 \
--health /api/healthz --mem 1024 --cpu 200 --tasks 256 \
--link data --link custom -- ./gitea web

Three settings matter here:

  • $CP_APP_DATA is the directory that survives everything: deploys, rollbacks, even deleting the application. Configure the service to write there if it lets you.
  • --link <dir> is for services that insist on writing inside their own tree. The named directory is replaced by a symlink into the data volume on every deploy, so ./data keeps its contents while the rest of the program is replaced. Repeat the flag per directory.
  • Higher limits. The defaults (256 MB, half a core) fit a small API, not a service with a database and background jobs. See Configuration.

Each gets its own unit, its own socket and its own limits. There is nothing to coordinate: no ports to allocate, no conflicts to avoid.

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

The paths are matched most-specific first, and everything that matches neither keeps being served from the docroot. The internal ports only have to differ from each other within the same account, and even that is a convention rather than a requirement — each application runs in its own network namespace.


Small, stateless, and it must be up when a third party calls.

Terminal window
corepanel app create <account-id> hooks --domain example.com --path /hooks --port 8090 \
--health /healthz --mem 128 --cpu 25 --tasks 32 -- ./receiver

Two things worth doing for this shape:

  • Keep the limits small. A receiver that leaks memory should hit its ceiling and be restarted, not consume the machine.
  • Give it a health path anyway. A receiver that accepts connections but fails every request would otherwise pass the deploy check and start dropping deliveries silently.

The WAF sits in front, as it does for every path on the domain. If a legitimate provider trips a rule, add a trusted IP or a per-path exception rather than disabling protection for the whole site.


A service you want to reach only from your own network

Section titled “A service you want to reach only from your own network”

CorePanel publishes an application under a public hostname with a public certificate. There is no “internal only” switch today, so the access control has to be yours:

  • Put it on a hostname nobody guesses, and require authentication in the application itself. A hostname is not a secret — it appears in certificate transparency logs — so this alone is not enough.
  • Or restrict at the firewall, and reach it over a VPN or an SSH tunnel to the socket.

Do not rely on the port being unreachable: it already is. What is reachable is the hostname, and that is what needs the lock.


  • A cron job. Something that runs and exits is a cron job, not a supervised process. An application that exits is restarted, forever.
  • A worker with no HTTP. The deploy check and the publication both assume the program serves HTTP on $PORT. A queue worker with no listener will fail its health check.
  • A service you did not build. If it ships its own systemd unit and its own upgrade path, a reverse proxy route leaves both alone.
  • A WordPress or PHP site. Those are sites, with their own tooling — see the WordPress section of the panel.