Repository navigation
Levelrail v0.5.0: built-in observability, Pages and safer git deploys #1241
thegdsks
announced in
Announcements
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.
Levelrail v0.5.0 is out, and it is the release where "why was this app slow at 3am" stops sending you to another tool. Logs, traces, errors, request ids and metrics stay on your box until you point them somewhere, and you can read them in the dashboard when you do not. Alongside that: Pages (static sites straight from git, with digest-checked rollback), a sleep history that shows what scale-to-zero actually saved, and a git deploy path that handles duplicate webhooks, wrong-repo pushes and failing builds without drama. Upgrading is one command and your data stays where it is.
Everything below is in the v0.5.0 release (PR #1223). Between v0.4.0 and v0.5.0, 456 files changed, with about 39,500 lines added and 5,400 removed.
This post is a snapshot of v0.5.0. Later releases are listed on the releases page and the feature status page tracks what is stable today. The upgrade commands below work with any newer
LEVELRAIL_VERSIONtag too.Contents
Upgrade in one command
On an existing install, this keeps your unit file, ports and data, restarts the service and waits for it to come back healthy:
curl -fsSL https://levelrail.com/install.sh | sudo sh -s upgradeTo land exactly on this release:
curl -fsSL https://raw.githubusercontent.com/glincker/levelrail/main/install.sh \ | sudo env LEVELRAIL_VERSION=v0.5.0 sh -s upgradeDocker Compose users pin the image tag (multi-arch, signed with cosign):
New to Levelrail? Run the same line without
upgradeon a Linux host.Tip
If you want the database rolled back along with the binary when something goes wrong, use the safe self-update.
levelrail-cli upgraderuns read-only preflight checks (release signature, Docker Engine, free disk, a recent control plane backup), andlevelrail-cli upgrade --applydownloads the release, verifies the checksum and cosign signature, snapshots the database, rehearses the migrations on a copy, swaps the binary and waits for health. If the new release is not healthy in 90 seconds, the old binary comes back, and so does the old database when the schema changed. The installer cannot undo a schema migration, which is why this path exists. Details: Installing and Upgrade safely.Important
From v0.5.0 on, the dashboard Upgrade now button shows live progress: the ordered steps (check, download, verify checksum, verify signature, probe, back up, migration dry run, stop, swap, start, health check), elapsed time and an expandable log. It is journaled on disk, so a page reload or a control plane restart picks the same run back up. v0.5.0 also fixes the launcher behind that button, which now passes its working directory and environment and records early failures. Because that fix ships in this release, a host older than 0.5.0 may not complete the hop from the button. Make that one hop with the command above, or run
sudo levelrail self-upgrade --to v0.5.0on the host from the data directory (/var/lib/levelrail-databy default). Every upgrade after that can be driven from the button.Highlights at a glance
levelrail-cli export destinations createlevelrail-cli apps log-pipeline setexport destinations create --kind metrics_remote_writelevelrail-cli apps log-budget setlevelrail-cli apps tracing enablelevelrail-cli apps error-tracking enableX-Request-Idandtraceparenton every request, recent requests per applevelrail-cli apps accesslevelrail-cli apps create --file app.yamllevelrail-cli apps exposurelevelrail-cli apps sleep historylevelrail-cli fixeslevelrail-cli import <repo>Observability
Telemetry still stays on the node by default. v0.5.0 adds the exits and the entrances: send it to tools you already pay for, or read it from Grafana without running a shipper.
Send logs to a vendor with just a key
There are four new destination kinds:
logs_newrelic,logs_datadog,logs_axiomandlogs_betterstack. Their endpoints are preset, so all you supply is a key. Each line goes out with the app name as the service, its stream and level, and parsed fields for structured lines.The key is stored encrypted and never returned. The queue is mirrored on disk, so lines a down receiver has not accepted survive a control plane restart and go out after it. A batch that fails after its retries stays queued instead of being dropped. Set
APP_EXPORT_SPOOL=falseto opt out.Per-app log pipelines
Rules run on every line before storage, live tail and export: multiline joins, redaction, drops and regex parsing with named groups. New rules reach new lines within about 15 seconds, and you can preview them against sample lines first.
levelrail-cli apps log-pipeline preview web --file rules.json --input lines.txt levelrail-cli apps log-pipeline set web --file rules.json[ { "kind": "redact", "pattern": "password=[\\S]+", "replace": "[REDACTED]" }, { "kind": "drop", "pattern": "GET /healthz" } ]Read logs from Grafana, push metrics to Prometheus receivers
The control plane answers a read-only subset of the Loki HTTP API over the node-local store. In Grafana, add a Loki data source with URL
https://your-host/api/v1/loki, basic auth with any user and areadtoken as the password, then run{app="web"} |= "timeout"in Explore. Metric queries and parsers get a clear Loki-style refusal rather than a guessed answer.For metrics, a
metrics_remote_writedestination pushes the same series as the scrape endpoint, so Grafana Cloud, Mimir and VictoriaMetrics can ingest it:GRAFANA_TOKEN=glc_xxx levelrail-cli export destinations create --name grafana --kind metrics_remote_write \ --endpoint https://prometheus-prod-01.grafana.net/api/prom/push \ --username 123456 --api-key-env GRAFANA_TOKENLog volume budgets
One chatty app should not push everyone else's history off the disk. A budget caps stored log bytes per app and trims only that app's oldest lines.
levelrail-cli apps log-budget set web --max-mb 512 levelrail-cli logs usagelogs usagelists apps noisiest first, with stored size, ingest rate, budget, percent used, a days-until-full forecast and trimmed lines.Traces, error tracking and the edge request log
Traces. Turn tracing on for an app and you get an ingest key and three standard environment variables. Any OpenTelemetry SDK with an OTLP/HTTP exporter works (gRPC is not accepted on this endpoint). Nothing is stored until you turn it on.
levelrail-cli apps tracing enable web levelrail-cli apps traces list web --errors --min-ms 250The dashboard lists traces newest first, filters by window, minimum duration and errors only, and opens a waterfall. Spans age out with the log retention window.
Error tracking. Keep the Sentry SDK you already have and change the DSN. Events are grouped into issues by fingerprint (the SDK's own, else exception type plus the innermost in-app frame, ignoring line numbers), and each issue is tied to the deploy that was current when it arrived. A resolved issue that fires again reopens as a regression. Values under keys like
authorization,cookie,password,secretandtokenare scrubbed before storage.levelrail-cli apps error-tracking enable web levelrail-cli apps errors list web --status open --since 24hEdge request log. Every request through the edge gets an
X-Request-Idand a W3Ctraceparentbefore it reaches your app, and values the client already sent are kept so an upstream proxy stays in the same trace. The control plane holds recent requests per app in memory (1000 per route by default, set withAPP_ACCESS_LOG_SIZE, cleared on restart) and never records query strings or client addresses.Full reference: Observability.
Pages
Pages deploys a static site from git with no container. Set
build.type: staticinapp.yaml, push, and the embedded ingress serves the published files with automatic TLS on your domains.From the CLI, create the app from that spec. In the dashboard, the New wizard's Git repository card does the same.
Each deploy publishes into a content-addressed directory and records a hash of the published tree. Rolling back re-publishes the earlier tree if its files are still on disk and unchanged. It answers 410 when the tree is gone and 409 when the content changed, so a rollback never quietly serves something different from what you shipped. If a deploy fails, the previous version stays live.
Docs: Pages.
Deploys
A lot of this release goes into the unglamorous part: pushes that arrive twice, arrive for the wrong repository, or ship a build that cannot start.
flowchart TD Push["Git push webhook"] --> Sig{"Signature valid?"} Sig -->|no| Rej["Rejected, kept in delivery history"] Sig -->|yes| Dup{"Seen this delivery id?"} Dup -->|yes| Same["Original status, note: duplicate delivery"] Dup -->|no| Repo{"Right repository and new commit?"} Repo -->|no| Ign["Ignored, with the reason"] Repo -->|yes| Q["202 deploy queued"] Q --> Build["Clone and build in the background"] Build --> Ready{"Candidate ready?"} Ready -->|no| Keep["Old container keeps serving"] Ready -->|yes| Cut["Traffic cutover"]X-GitHub-Deliveryand equivalents) that already succeeded gets the original status and the noteduplicate delivery.202 deploy queued: <id>as soon as the attempt is recorded. Clone and build run in the background, and the delivery's note updates when the deploy ends.APP_NETWORK_POOL(default10.88.0.0/14, room for about 1000 apps). Orphaned networks are removed when an app is deleted, and a pool check warns above 80 percent use. Existing networks are never re-addressed.Note
Direct mode is plain HTTP and bypasses TLS, basic auth and sign-in gates on the domain. Use it to check that an app runs, not as its public address. The firewall rule has not been exercised against a real ufw yet, so treat it as new.
Docs: Git integrations, App exposure, Troubleshooting.
Everyday ops
Sleep history, fix-it engine, and dashboard polish
Sleep history with wake latency. Every sleep and wake is recorded with a reason (
idle_timeout,manual,request,api) and who caused it. Awokeevent carries how long the app slept and how long the wake took, and a wake that is not healthy within 30 seconds recordswake_failed. Totals cover asleep time over 7 and 30 days, average and 95th percentile wake latency, and memory saved in GiB-hours when the app has a limit. Client addresses are never stored.levelrail-cli apps sleep history web --since 7d --limit 50Fix-it engine. A failing readiness or doctor check becomes a typed fix you can preview, apply and re-verify. Each fix has a tier (
auto,guided,manual), a risk label and a list of concrete changes shown before you confirm. Attempts are remembered: a failed fix drops one tier, says what it tried, and re-arms only when the failure changes. Built-in fixes cover the mesh public address, WireGuard privileges, a held UDP port, disk reclaim (rollback images are kept) and orphaned app networks.Dashboard polish. App pages get a one-row header with inline help and status chips (including a moon chip with time asleep), and a page width setting offers Comfortable, Wide and Full layouts.
Docs: Sleep when idle, Fix-it engine.
Builds
Ruby and PHP projects are now accepted by the import flow, and the plan fills in what each start command expects: the port, the health path and the environment variables that must be set. Rails and Laravel starter apps are coming to the examples.
config/puma.rb)RAILS_ENV=production,RAILS_SERVE_STATIC_FILES=true,SECRET_KEY_BASErequiredrackupAPP_KEYrequired, runsphp artisan migrate --forceon startOn a Laravel app with no database yet, set
RAILPACK_SKIP_MIGRATIONS=true. Docs: Importing apps. The release also maps brand marks for 136 more catalog templates.Numbers
Only counts we can point at:
linux/amd64andlinux/arm64, signed with cosign with SBOM and provenance attached..deband.rpmagent packages and sigstore-signed checksums.We are not publishing idle footprint or throughput numbers here. That measurement is still open work on the roadmap.
Upgrade notes
Important
Read this before you upgrade a production host.
0785_webhook_delivery_ids(webhook idempotency),0790_sleep_events(sleep history) and0795_fixit(fix-it attempt memory). They are forward-only.install.sh upgradecannot undo them, whilelevelrail-cli upgrade --applyrehearses them on a copy and can restore the pre-upgrade snapshot.levelrail-cli upgrade backup-nowtakes the control plane backup the preflight asks for.APP_LOG_BUDGET_MBis unset by default (unlimited).APP_NETWORK_POOL(default10.88.0.0/14). It must not overlap your host interfaces, routes or other Docker networks. Set it tooffto use the Docker daemon's own pools.Thank you and what is next
Thanks to everyone who filed issues and tried the betas on real servers. Contributor credit for v0.5.0 goes to @thegdsks.
Next on the roadmap: Phase 5 hardening (chaos tests, upgrade and downgrade tests across versions, a published idle-footprint number) and more maturity labels in feature status, so you can see which features are proven and which are new. Your feedback on the observability pieces decides what gets deepened first.
Try it
/api/v1/loki.All reactions