SpipCP
Deploys

Git deploys

Deploy a site from git — scoped deploy keys, the fetch→build→release pipeline, zero-downtime cutover, one-click rollback, and push-triggered redeploys.

A php, static, or node site can deploy straight from a git repository. Connecting a repo makes the panel generate a scoped deploy key, and every deploy runs the same pipeline: fetch β†’ build β†’ stage a release β†’ smoke-check β†’ cutover. A bad build never touches the live site, and any deploy is one click away from rollback.

Which site types deploy from git

Seven of the eight do. WordPress does not, and the panel refuses rather than letting it half-work.

The reason is that the two models contradict each other. Git treats the repository as the truth and the server as disposable. WordPress treats the server as the truth: plugins and themes install through its own admin, core updates itself, and media lands in wp-content/uploads. A site that rewrites its own directory cannot also have that directory replaced on every push β€” and because WordPress core lives inside the release, deploying anything short of a full WordPress checkout would remove wp-admin and wp-includes and the site would stop existing.

So connecting a repository to a WordPress site is refused, with that explanation, at the point of connecting rather than at the point of damage. To move a WordPress site between servers, use Backups; to change files, use the file manager or SFTP.

Docker apps build the connected repository

A Docker site's deploy rebuilds its image from the deployed release β€” the compose file's build context follows current, so each deploy builds the commit it just fetched. A site that has saved its own compose in the Compose tab keeps it; the build-from-source default applies to a site that has never edited one.

Connecting a git source

On the site workspace's Deploys tab, set the repository. A git account can be connected to browse repos and branches and register the deploy key in one click, or a clone URL can be pasted by hand β€” both reach the same pipeline below. Either way, set:

  • Repository URL β€” the SSH clone URL (git@host:owner/repo.git) for a private repo, or any clone URL for a public one.
  • Branch β€” the branch deploys track (e.g. main).
  • Deploy key β€” for a private repo, the panel generates a deploy key scoped to this site only. Its name defaults to <site>-deploy (rename it before generating if a different name is preferred). Copy the public half and add it as a deploy key on the repository (not an account key). The private half is encrypted at rest and never leaves the panel.

Per-repo keys, never broad tokens

Each site gets its own deploy key, scoped to its one repo. A key can't fetch another site's repo. A broad provider token (a personal access token, an org token) is never stored and never reaches a node β€” the per-repo deploy key is the only credential. Revoking a key makes the next deploy fail with a clear message until a new key is generated.

The release layout

Inside the instance, under the site's own directory:

/srv/web/<site>/            the site directory β€” a real folder, never replaced
β”œβ”€β”€ releases/<timestamp>/   one immutable release (the fetched + built tree)
β”œβ”€β”€ shared/                 state that survives a deploy (per type β€” see below)
└── current -> releases/…   the symlink the web server serves through

This structure exists from the moment a site launches, not from its first deploy. A site with no git source at all still serves through current, pointing at the release its launch seeded β€” so connecting a repo later is a deploy like any other, with no conversion step and no different path.

A deploy builds a new releases/<timestamp> tree, links each of the site type's shared paths into it, smoke-checks it, and only then flips current to it in one atomic move. Because the cutover is a single swap, a request in flight during a deploy sees either the old release or the new one β€” never a broken half-state. Old releases are kept for rollback (the oldest beyond the retention limit are pruned).

Put anything that must survive a deploy in shared/

A release is immutable and eventually pruned. Each site type declares what belongs in shared/ β€” see what shared/ holds β€” and those paths are linked into every release and never removed. A file written directly into releases/<timestamp>/ disappears with that release.

For WordPress that means the media library and wp-config.php are both kept, so deploying a theme or plugin repository over a live site does not disturb either.

Checking for new commits before deploying

Check for updates β€” beside Deploy now β€” reads the head of the tracked branch on the repository itself and compares it against the commit the live release was built from. It answers the one question the deploy history cannot: whether deploying would change anything.

The result always names both commits, including when they match:

  • A newer commit is on main β€” the branch has moved ahead of what is serving; deploying brings it.
  • Up to date β€” the branch head is already the live release's commit, so a deploy would rebuild the same source.
  • Unknown, with the reason β€” no git source, a clone URL the provider API cannot address, no connected git account, a tracked branch the remote no longer has, or a provider that did not answer. A check that could not run is never reported as "up to date".

A repeated commit in the history is ambiguous β€” this is not

Two deploys recording the same sha can mean nothing new to fetch or the push never reached this repository, and the history cannot tell them apart. A repo connected to more than one remote makes that easy to hit: a push that lands on one of them leaves the other exactly as it was, and the deploy faithfully rebuilds the old commit. Comparing against the branch head before deploying separates the two cases.

The branch head is read over the provider API from the panel itself, so the check is immediate and touches neither the node nor the instance.

Rollback

The Deploys tab shows every deploy (who, what, when, sha, duration, log). Roll back flips current back to the previous release β€” and re-runs the smoke check on it before serving, so a rollback is as health-gated as a forward deploy. It's just as fast (no re-fetch, no rebuild β€” the release is already on disk).

Failure gating

Any failing step aborts before cutover, so the site keeps serving its current release:

  • a build failure (e.g. composer install errors) β†’ the deploy is marked failed with the failing step + a remedy; the new release is discarded;
  • a smoke-check failure (the new release doesn't serve) β†’ same, no cutover;
  • a failing migration hook (a blueprint-declared pre/post step like php artisan migrate) β†’ same, the release never goes live.

In every case the deploy history names the failing step and what to fix.

Webhooks

A site can redeploy automatically on every push β€” see Deploy from GitHub. Each site has its own webhook endpoint and secret. Deliveries are signature-verified, rate limited, and serialized β€” a push during a running deploy queues exactly one follow-up, so the final state is always the latest commit.

On this page