# Deploying

> How a release reaches a CorePanel application: the artifact, the swap, the health check, the automatic rollback, and going back to an earlier version.

Source: https://www.corepanel.net/docs/applications/deploying/
Last updated: 2026-07-28
Part of the CorePanel documentation — https://www.corepanel.net/docs

---

A deploy replaces the running program with a new build, verifies it actually serves, and
puts the old one back if it does not. The old release keeps serving throughout the parts
that take time.

## The artifact

A `.tar.gz` containing your **built** program — CorePanel does not compile anything. Its
contents become the release root, so paths in the start command are relative to it:

```
api.tar.gz
├── server           ← the binary
├── templates/
└── static/
```

```bash
corepanel app create <account-id> api --domain example.com --port 8080 -- ./server
```

There are three ways to get it onto the server. By hand, the shortest is to [drop it into
the panel](#deploying-from-the-panel), which uploads and deploys in one go. From a build
pipeline, [POST it and let the deploy run on the same
call](https://www.corepanel.net/docs/applications/ci-cd/#upload-and-deploy-in-one-call) — no FTP account involved.
For an artifact too large for either, upload it **as the account, over FTPS**, to the
conventional location:

```bash
curl --ssl-reqd --ftp-create-dirs -T api.tar.gz \
     --user 'example:<ftp password>' \
     ftp://example.com/apps/api.tar.gz
```

> **There is no SSH for hosting accounts**
>
> Hosting accounts are created with `nologin`: they have no shell, so **`scp`, `sftp` and
> `ssh` do not work** for them. The file transfer channel is FTP over TLS, which CorePanel
> requires — a plaintext FTP session is refused. FTP sessions are chrooted to the account's
> home, so the path above is `~/apps/api.tar.gz`.
With `lftp`, which handles the directory and the TLS negotiation in one line:

```bash
lftp -u 'example,<ftp password>' -e \
  'set ftp:ssl-force true; mkdir -p apps; put api.tar.gz -o apps/api.tar.gz; bye' \
  example.com
```

The FTP password is the **account's FTP password**, a separate secret from the panel
password. Set or rotate it from the panel, in the account's access settings.

`~/apps/<app>.tar.gz` is what `deploy` picks up with no arguments. Any other path inside
the account's home works too:

```bash
corepanel app deploy <account-id> api --artifact uploads/api-2026-07-27.tar.gz
```

The archive must live **inside the account's home** and **belong to the account** — it is
read and extracted as root, so a path that resolves outside the home, a symlink pointing
out of it, and a file owned by anyone else are all refused. A file uploaded over FTP is
always owned by the account; one an operator placed by hand may not be, and `chown
<account>:<account>` is the fix. Extraction validates every entry too: one that tries to
write outside its destination is rejected rather than followed, and extended attributes
are not preserved, so an archive cannot smuggle in file labels that would keep the program
from starting.

## Deploying from the panel

Open the application from the account's **Applications** section and press **Deploy**. The
only thing to decide is where the archive comes from:

![The Deploy dialog: an Archive field defaulting to apps/api.tar.gz, and a warning that a release which does not answer within 45 seconds is rolled back automatically](https://www.corepanel.net/_astro/apps-deploy-dark.BrBUmY1f.png)

**Upload an archive** takes the `.tar.gz` from the machine you are sitting at — drop it on
the dialog or click to pick it — and uploads it straight to the application. Nothing else
is needed: no FTP client, no FTP password, no file placed in advance. The upload runs in
pieces with a progress bar, so a build of a few hundred megabytes is not one long request
that a dropped connection undoes: a piece that fails is re-sent on its own, and **Cancel**
stops the transfer and discards what had arrived. When the last piece lands, the deploy
starts by itself — same extract, same health check, same automatic rollback.

**Already on the server** is the other tab, for an archive that is in the account's home
already. Leave the field empty for `apps/<name>.tar.gz`, or name any other path inside the
home — `uploads/api-2026-07-27.tar.gz`, for instance. This is the route for an artifact
too large to upload through the browser, and the one to use when the file got there over
FTP or from a pipeline.

The dialog blocks while the deploy runs — the extract, the swap, the restart and the
health check are one operation — and the outcome is a dialog, not a toast, because there
are two outcomes worth reading. **Deploy complete** names the release now serving. The
other one is this:

![The result dialog of a rolled-back deploy, naming the release that did not answer, the one serving again, and the tail of the journal explaining why](https://www.corepanel.net/_astro/apps-rollback-dark.BjDQWt82.png)

That is the safety net, not an error: the site never stopped serving. The tail of the
journal is included so the reason is on screen, and the same text stays on the application
as **Last failure** after you close the dialog.

## What a deploy does, in order

1. **Extract** the archive into a new release directory. The old release keeps serving.
2. **Prepare** it: permissions, the symlinks for any [linked
   directories](https://www.corepanel.net/docs/applications/configuration/#linked-directories), security labels.
3. **Swap** `current` to the new release and restart the service — milliseconds of cut.
4. **Verify**: connect to the endpoint and, if a health path is set, request it. Up to 45
   seconds, retried.
5. **If it does not answer**, put the previous release back, restart, and report the deploy
   as rolled back with the tail of the journal explaining why.
6. **Prune** the releases the retention policy no longer keeps.

Only steps 3 and 4 touch production, and step 5 is why a bad build is an inconvenience
rather than an outage.

> **A rolled-back deploy is not an error**
>
> The command **succeeds** — the site is serving, just not the version you asked for. The
> CLI exits non-zero and prints the reason; the panel shows the application as running with
> the previous release active. Read the outcome, do not assume it from the exit of the
> upload step.
## Releases and rollback

In the panel, the **Releases** card on the application lists what is on disk, with the one
currently serving marked **Active** and a **Roll back** button on every other row:

![The Releases card listing three releases with their creation date and artifact, the newest marked Active, each of the others offering Roll back](https://www.corepanel.net/_astro/apps-releases-dark.BB9hCfNT.png)

> **Roll back acts immediately**
>
> There is no confirmation step: pressing it restarts the application on that release there
> and then. The result dialog tells you how it went — and if the older release does not
> answer either, the one that was serving is restored.
The same, from `corepanel`:

```bash
corepanel app releases <account-id> api
```

```
RELEASE                 CREATED               ARTIFACT             ACTIVE
20260727T142212Z        2026-07-27 14:22      apps/api.tar.gz      yes
20260727T101500Z        2026-07-27 10:15      apps/api.tar.gz
20260726T173040Z        2026-07-26 17:30      apps/api.tar.gz
```

The list comes from disk, not from the database: those are the releases a rollback can
actually reach. **The three newest are kept**, plus the active one if it is not among
them — a release that is serving is never pruned, however old it is.

```bash
corepanel app rollback <account-id> api                       # the previous one
corepanel app rollback <account-id> api --to 20260727T101500Z # a specific one
```

A rollback is verified the same way a deploy is: if the older release does not answer
either, the current one is restored.

Rolling back changes **the program, not the data**. `$CP_APP_DATA` is untouched, so a
release that migrated a database schema forward will not un-migrate it by going back —
that part is yours to plan.

## The first deploy is special

An application is registered without being published. The route that sends traffic to it
is added by the **first deploy that answers**, because pointing a site at a socket nothing
is listening on would turn that path into a 502 — and for an application mounted at `/`,
that is the whole site.

So the order is always: create, upload, deploy. Only then does the hostname serve
anything.

## Deploying without a person

Everything above can be handed to a build pipeline with a token that authorises exactly
one thing: [Deploying from CI/CD](https://www.corepanel.net/docs/applications/ci-cd/).

## Common failures

| What you see | Usually means |
|---|---|
| Deploy reports `rolledBack` with `connection refused` | The program exited immediately, or it is not listening on `$PORT` |
| Deploy reports `rolledBack` with a 404 on the health path | The program serves, but that path does not exist — check for the prefix strip on a sub-path mount |
| `the artifact was not found` | It is not at `~/apps/<app>.tar.gz`, or it was uploaded as root instead of the account |
| The program starts by hand but not under CorePanel | It probably depends on the working directory or on a variable from your shell. The working directory is the release root; everything else must come from [environment variables](https://www.corepanel.net/docs/applications/configuration/#environment-and-secrets) |
| `a deploy is already running for this application` | One deploy at a time, by design. Wait for it |

More in [Running and troubleshooting](https://www.corepanel.net/docs/applications/operations/).
