Skip to content

Spawner 2.2.0

Choose a tag to compare

@github-actions github-actions released this 10 Oct 13:38
· 8 commits to master since this release
Immutable release. Only release title and notes can be modified.

Spawner is an open source, self-hosted preview environment manager: a copy of your app for each git branch, on your own server, with its own URLs, database and logs, for teams and their coding agents. Documentation, changelog.

A harder Spawner: environments kept from the cloud's metadata and the server's services, source repositories chosen by admins, Traefik 3.7 and releases you can verify. And its documentation, now on https://spawner.run.

  • Environments no longer reach the cloud's metadata or the server's services: the code of a branch could read the credentials of the server from the metadata service of its cloud (AWS, GCP, Azure, Hetzner, OVH...), or call a service listening on the server. A new spawner-firewall container loads rules for every container of Docker's bridge networks: the metadata addresses are refused, but for DNS, and the server answers on DNS, HTTP and HTTPS only. The installer also has Docker load them before it starts, so that they hold from boot on: after an update from the dashboard, run install.sh --upgrade once on the server for that part.
  • Source repositories are chosen by admins: the other sources of spawner.yaml must come from repositories listed in the project (Projects, Edit, "Source repositories"); spawner up refuses the others before sending anything. A branch could otherwise make Spawner clone any repository its deploy keys reach, or an address of the internal network. The upgrade lists for each project the repositories its live environments already use.
  • Traefik 3.7: Traefik 3.5, which no longer gets security fixes, gives way to 3.7; upgrading Spawner pulls it. Traefik 3.7 embeds lego v5, which dropped the DNS providers azure (use azuredns), dnspod, googledomains, cloudxns, brandit, iwantmyname and iij (use iijdpf): a server set up with one of them switches before upgrading. Request headers whose name holds a character other than a letter, a digit or a dash (X_Spawner_Preview, X.Forwarded.For) no longer reach applications, so an application that reads headers the PHP or CGI way cannot be handed one that Traefik removes or sets.
  • What you download can be verified: the images, install.sh and the CLI bundle of a release carry build provenance attestations (gh attestation verify install.sh -R Flosk6/Spawner). The install.sh of a release names the digest of its image, and updates from the dashboard read it there: both run the image the release built, whatever its tag points to later. Postgres and Traefik are pinned by digest.
  • Terminals and log streams close when their access goes: deactivating a user, changing their role or revoking a token now closes the terminals and the streams of logs they had open, at once, and an expired token's within 30 seconds.
  • Uploads stop at their first limit: an archive is read as a stream that stops at the first file, byte or decompressed byte over the limits, so that a compression bomb costs no more than a legitimate archive; a person has 5 deploys of uploaded code waiting to start at most.
  • A harder installation:
    • The installer no longer makes a token of the installation with every scope (SPAWNER_BOOTSTRAP_TOKEN) unless given one; installations that have one keep it until you empty its line in .env.
    • It refuses a version older than the installed one (its database only migrates forward; --allow-downgrade after restoring a backup), a data directory other than the installation's, and system directories; it never makes new secrets over an installation that lost them, never upgrades without a backup (it starts Postgres for it), writes .env and dns.env in one move, keeps a trailing = of their values, and no longer shows the DNS credentials typed.
    • The data directory is mode 700, rendered compose files and logs are readable by Spawner only, Traefik runs without capabilities but binding ports, Postgres and Traefik have memory limits, Spawner's code belongs to root, and Prisma's telemetry is off. A local install over HTTP warns that it serves every interface; the development stack listens on 127.0.0.1.
    • git reaches GitHub, GitLab and Bitbucket with the host keys they publish, even the first time.
  • The installer warns below 4 GiB of memory, where no environment starts with the default settings, and names the settings to lower. It accepts an /opt/spawner holding only .env and dns.env, as when restoring on a new server.
  • Smaller hardening:
    • Previews: a cookie a preview sets under the name of Spawner's no longer locks its visitors out; share links opened on public URLs never reach the application; the waiting page of a public URL no longer says why its environment failed; logging out takes the preview cookie off the browser; the cache of preview hosts is bounded.
    • The dashboard's page has a Content-Security-Policy that runs its own scripts only; every answer carries Referrer-Policy, and over HTTPS Strict-Transport-Security.
    • Accounts: passkeys must verify their user (a PIN, a fingerprint); names are unique, whatever their case; renames and linked GitHub accounts are audited, and every audit event records the address of its request; the CLI approval page says when and from where the login started. Removing someone from the GitHub organization does not deactivate them in Spawner: do it on the Team page.
    • Compose: an environment has 4 CPUs and 4096 processes to share between its services (SPAWNER_ENV_CPUS, SPAWNER_ENV_PIDS), as it has its memory, and no service gets raw sockets (NET_RAW). A file asking more CPUs or processes than that is refused.
    • Uploads: links are followed through the links they lead to once extracted.
    • CLI: spawner login opens http and https URLs only; untracked .env-*, .npmrc, private keys and *.pem files stay home like .env files; a Dockerfile is read only when it is a regular file of 1 MiB at most; the MCP server fences what comes from the environment and tells the model to read it as data.
  • The documentation moves to https://spawner.run, with new pages: a quickstart from a fresh server to a first environment; previews for pull requests from GitHub Actions and GitLab CI; the two example projects; troubleshooting. Also new: every key of spawner.yaml and the codes of a refused .spawner/, what an application needs to run behind Spawner, every option, variable and error code of the CLI and what each MCP tool takes and returns, VS Code with GitHub Copilot, Gemini CLI and Devin Desktop for agents, and what Spawner does not do.
  • Corrections to the documentation: the minimum memory is 4 GiB, not 2; the compose policy listed platform under build as allowed, which Spawner refuses; services without mem_limit share what is left of the environment's memory, up to 512 MiB each; public URLs (auth: none) never wake a sleeping environment; with GitHub login set to an organization, its members get an account at their first login, without an invitation; admin.js invite links last 24 hours; the Dockerfile example of "Dockerfiles that share their layers" builds again; removing Spawner and the local install download the installer from the release, as upgrades do.
  • spawner init links spawner.yaml to https://spawner.run/docs/manifest/ instead of an outdated branch of the repository, and the instructions it writes for coding agents say what to do on exit code 6. The dashboard's Docs button opens https://spawner.run/docs/. spawner login says a 90-day token expires in 90d, not 89d 24h.
  • The MCP server is listed on the official MCP Registry as io.github.Flosk6/spawner, and spawner-cli on npm links to https://spawner.run. Release pages say what Spawner is, give the install and upgrade commands apart, and show how to check the files against SHA256SUMS.

Upgrading from 2.1.0: update from the dashboard or with install.sh --upgrade, then run install.sh --upgrade once on the server, so that Docker loads the firewall rules of the environments before it starts any container (an update from the dashboard installs the firewall container, not Docker's part). Before upgrading, a server set up with a DNS provider that lego v5 dropped switches to another one (see Traefik 3.7 above). The upgrade lists, for each project, the repositories its live environments already take sources from; new ones need an admin. An installation from 2.1 or before keeps its bootstrap token until you empty its line in /opt/spawner/.env.

Install

On a new server:

curl -fsSL https://github.com/Flosk6/Spawner/releases/download/v2.2.0/install.sh | sudo bash -s -- --version 2.2.0

Upgrade

From the dashboard: Update to 2.2.0 on the System page. From the server:

curl -fsSL https://github.com/Flosk6/Spawner/releases/download/v2.2.0/install.sh | sudo bash -s -- --upgrade --version 2.2.0

Both back the database up first, and go back to the previous version if the new one does not start.

Files

  • install.sh: the installer of this version.
  • spawner: the CLI and MCP server in one file, for Node.js 20 or later. Or npm install -g spawner-cli@2.2.0.
  • SHA256SUMS: their checksums.
  • Image: ghcr.io/flosk6/spawner:2.2.0 (sha256:1f045be15c39aacc36fab3b86cc19fa6fb028b8daf036bb4a3d0ba6687dd8f42, linux/amd64, linux/arm64).

Verify

The image, install.sh and spawner carry build provenance attestations from this repository's release workflow:

gh attestation verify install.sh -R Flosk6/Spawner
gh attestation verify oci://ghcr.io/flosk6/spawner:2.2.0 -R Flosk6/Spawner

Or compare the files with their checksums: curl -fsSL -O https://github.com/Flosk6/Spawner/releases/download/v2.2.0/install.sh -O https://github.com/Flosk6/Spawner/releases/download/v2.2.0/SHA256SUMS && sha256sum -c --ignore-missing SHA256SUMS