plgsys is the app-side deploy client for the Nucc app-hosting platform: a
single bash script that drives the whole release cycle from inside an app
repository — build, upload, enable, roll back — against the platform's
remote deploy API, using a scoped bearer token. No platform checkout, no
infrastructure credentials, no SSH.
# Homebrew (tap: github.com/Nucc/homebrew-plgsys; head-only formula)
brew tap nucc/plgsys
brew install --HEAD nucc/plgsys/plgsys
# or the installer (CI, Linux) — installs to ~/.local/bin
curl -fsSL https://raw.githubusercontent.com/Nucc/plgsys/main/install.sh | shOr just vendor the plgsys script into your app repo (e.g. bin/plgsys) —
it is self-contained. plgsys ci does this for you (see below).
Requirements: bash, curl, git, jq, and podman (images are built
locally).
# one-time, in the app repo
plgsys login https://platform-staging.example.com # paste a scoped API token
plgsys init # short interview → .plgsys.conf
# every release
plgsys deploy staging # build + upload + enable
plgsys deploy production 1.4.2 # promote the same artifactThe app must already be registered on the platform (an operator runs
bin/platform app provision <app>); init discovers and confirms the
server-side manifest, then writes only build settings locally.
plgsys init interview: build config (thin) or manifest + build config (operator)
plgsys login [api-url] store the API token for a control plane (thin mode)
plgsys ci [env] write a GitHub Actions workflow: deploy on every push (default env: staging)
plgsys build [version] podman build + save (gzipped) → dist/<app>-<version>.tar.gz
plgsys deploy <env> [version] build (or reuse artifact) + upload + enable
plgsys upload <env> [version] ship a build (storage + host) without activating it
plgsys enable <env> <version> activate an uploaded version (rolling update)
plgsys rollback <env> [version] back to the previous (or a specific) version
plgsys versions <env> uploaded versions on the host, active one marked
plgsys builds builds in the shared storage (last 10 kept)
plgsys history deployment log
plgsys logs <env> app logs
plgsys status <env> app status (job + memory per allocation)
plgsys secrets list <env> secret keys for this app (never values)
plgsys secrets set <env> <KEY> store a secret (value prompted, or piped on stdin)
plgsys secrets unset <env> <KEY> remove a secret
<env> is production or staging.
plgsys init writes .plgsys.conf in the app repo root — commit it. Thin
mode (recommended) needs only the API endpoint(s) plus build settings:
APP_NAME=myapp
PLATFORM_API_STAGING=https://platform-staging.example.com
PLATFORM_API_PRODUCTION=https://platform.example.com
CONTAINERFILE=Dockerfile
BUILD_CONTEXT=.
ARTIFACT_DIR=distSetting PLATFORM_DIR instead selects operator mode, which delegates to a
local platform checkout (full access: master key + SSH).
Tokens are never stored in the repo: plgsys login keeps them per API host
under ~/.config/nucc-platform/tokens/, and CI supplies $PLATFORM_TOKEN.
Mint tokens in the platform web UI (app page → API tokens); they are scoped
per app, environment and capability.
plgsyswas formerly calledappctl— a legacy.appctl.confis still read when no.plgsys.confexists.
Without an explicit version, an exact git tag on HEAD wins; otherwise the
highest counter-style version across the build storage, the local artifacts
and the repo's git tags is incremented (v1, v2, v3, …). Builds require
a clean git tree and tag the built commit with the version, so every build is
reconstructable: git checkout <version> && plgsys build <version>.
plgsys ci # writes .github/workflows/deploy-staging.yml
git add .github && git commit -m "Auto-deploy to staging" && git pushThe generated workflow installs the latest plgsys from this repo's main
branch, builds the image on the runner and deploys through the remote API on
every push to the default branch. It needs one repository
secret (PLATFORM_TOKEN_STAGING / PLATFORM_TOKEN_PRODUCTION) — an
app-scoped, environment-scoped deploy token; ci offers to set it via gh
from your locally stored token.
The platform runs whatever the image does, provided it: listens on $PORT,
answers the configured health path with 200 when ready, reads
DATABASE_URL from the environment, and keeps state in the database (hosts
are replaceable).