Repository navigation
Levelrail Pages: host static sites from git on your own server, with rollback #1245
thegdsks
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
If you have pushed a folder of HTML to GitHub Pages or Cloudflare Pages, you know the workflow Levelrail Pages follows: point at a repo, get a site on a domain with TLS, undo a bad push. What changes is where it runs. Pages is served by the Caddy ingress embedded in your own control plane, on your own Linux box. There is no container, no registry and no build image involved.
Below: what a Page is, how to deploy one, what a deploy looks like, how rollback and delete behave, and a checklist for moving a site off GitHub Pages. The workflow is simple. You build the site in CI (or commit the output), and Levelrail publishes the finished files.
Contents
What Pages is
A Page is an app whose
build.typeisstatic. Levelrail fetches your repository, copies the directory you name into a folder on the control plane keyed by service and commit, and points your domains at it. Nothing runs per site. No container starts, no port gets allocated, no process needs watching. Caddy's file server, inside the same binary as the control plane, answers the requests.In practice a static site costs disk and nothing else. TLS and domain routing work exactly as they do for container apps, and because every deploy lands in its own commit-named folder, keeping old versions around for rollback is cheap.
Note
The workflow: build the site wherever you like (CI, your laptop), put the finished HTML in a directory or branch, and let Levelrail publish that directory. There is no build step on the Levelrail side for
static. Details under Limits.How it compares
Every cell for GitHub Pages and Cloudflare Pages comes from their official docs, linked in the row. Where I did not confirm something, the cell says "see docs" rather than guessing.
static; deploy finished filesdomains, automatic Let's Encrypt TLS_headersfile, max 100 rules (limits)/for unknown paths when there is no top-level404.html(docs)Cloudflare Pages is ahead on pull request previews, headers files and SPA routing. Levelrail Pages wins on placement: the site sits on hardware you control, next to your apps and databases, with the same dashboard, domains and rollback history as everything else you run.
Deploy a site
Put an
app.yamlin the repo. For a site whose finished output lives indist/:pathis optional and defaults to the repository root. It must be relative and point to a directory that exists in the repo. A static service cannot setport,host_portorbind_address, since there is no container to route to, andhooks,jobsandegressare rejected for the same reason. The full field list is in the app spec reference.Validate locally with no API call, then create the app:
For
build.type: staticthe CLI needs a repo and ref (--refdefaults to your current branch, thenmain) and refuses image-oriented flags such as a port or--image-repo. In the dashboard, open the New wizard and pick the Git repository card.If your generator is Hugo, Vite, Jekyll or a Next.js static export, run it in CI, commit or push the output to a branch such as
deploy, and give--refthat branch. Levelrail then publishes exactly the files you built.app.yamlwithbuild.type: staticto your repolevelrail-cli apps validate --file app.yamlThe deploy lifecycle
A static deploy skips the container pipeline. It goes through these stages:
The dashboard shows two steps, Fetch source and Publish to file server. With no container there is no readiness gate or health check, so the deploy settles as soon as the files are published and the route points at them. The digest recorded on the deploy is what later makes rollback safe.
Inspect a deploy from the CLI:
When a deploy fails, say because
build.pathdoes not exist in the repo, the previous version stays live until you fix it and push again.Domains and TLS
Domains behave as they do for container apps. List them in the spec, or manage them from the CLI:
Each domain gets an automatic Let's Encrypt certificate, renewed by the ingress. A domain can belong to only one service across the spec. For verification, wildcards and running behind an existing proxy, see Domains and ingress.
Rollback
Each deploy records a hash of the published tree. To roll back to a past succeeded deploy:
Or click Rollback on an earlier deploy in the Deployments view. Rollback is a re-publish, not a rebuild: Levelrail checks the files still on disk against the digest that deploy recorded, points the site back at them and records a new succeeded deploy so the history shows what happened. It can refuse in two ways:
409 Conflict410 GoneBoth messages tell you to redeploy from source. That is the point of the digest: a rollback restores exactly what that deploy served, or it refuses. It never serves a quietly different tree.
Deleting a Page
Deleting the app removes the site, its published files and its route right away:
There is also a direct route for sites that outlived their app:
levelrail-cli static-sites list levelrail-cli static-sites delete my-site # same as: DELETE /api/v1/static-sites/my-sitestatic-sites deleteremoves a site's published files, domains and route. While the app still exists it answers409, with the message to delete the app instead. An unknown name answers404; success is204 No Content.A background sweep also removes static sites that have no app and no queued or running deploy, once they are older than a grace period. That protects a site whose app is still being created. The grace period defaults to 10 minutes. Set
APP_STATIC_ORPHAN_GRACEto a Go duration such as1hto change it. An empty,0, unparseable or negative value falls back to the 10 minute default (the last two also log a warning).Where this lives in the source
internal/deploy/static.go,static_publish.go,static_remove.gointernal/api/deploy_rollback_static.gointernal/api/static_sites_delete.gocmd/levelrail/preview_limits.goLimits
build.type: staticdeploys finished files. Run Hugo, Jekyll, Vite or a Next.js static export in CI, commit or push the output, then pointbuild.path(and--ref) at it.port,host_port,bind_address,hooks,jobsandegressare rejected on a static service, because nothing runs.Migrating from GitHub Pages
app.yamlwithbuild.type: staticand the rightbuild.pathCNAMEfile: domains now come from thedomainslistgithub.ioproject path prefixTroubleshooting
portportport,host_portandbind_address--repoand--ref; readapps deploys logsbuild.pathmissing in the repostatic-sites deletereturns 409apps deleteinsteadAPP_STATIC_ORPHAN_GRACELinks and next steps
If you move a site over, tell me in the comments what you migrated and what tripped you up, especially anything you relied on in GitHub Pages or Cloudflare Pages that is missing here. A star on the repo helps other people find it.
All reactions