README
¶
pkgm
A dependency-free, pure-Go package manager for pkgx
bottles — a single CGO_ENABLED=0 binary that runs on a FROM scratch image
with zero runtime dependencies.
The reference pkgm is a Deno/TypeScript script that shells out to
pkgx, deno, curl, openssl, zlib and xz — roughly 515 MB of
runtime closure just to install a package. pkgm replaces all of it with one
static ~9 MB binary:
| reference pkgm | go-pkgx/pkgm | |
|---|---|---|
| downloader | curl + openssl + nghttp2 | Go net/http (TLS built in, CA bundle embedded) |
| extractor | info-zip + xz | Go compress/gzip + ulikunitz/xz |
| script runtime | deno (~130 MB) | — none — |
| runtime deps | glibc + libgcc_s + … | none (static) |
FROM scratch |
✗ | ✓ |
Install
Linux / macOS — one line:
curl -fsSL https://go-pkgx.github.io/install.sh | sh
Windows — one line (PowerShell):
irm https://go-pkgx.github.io/install.ps1 | iex
The installer downloads the static binary for your os/arch from the latest
release, verifies it against
the release SHA256SUMS, and drops pkgm on your PATH
($HOME/.local/bin, or %LOCALAPPDATA%\Programs\go-pkgx on Windows). Set
PKGM_INSTALL to override the directory on Unix.
Go users:
go install github.com/go-pkgx/pkgm@latest
Once installed, pkgm install lz4.org verifies each bottle against the signed
registry by default.
Usage
pkgm install|i <pkg>[@version] ... install to /usr/local (root) or ~/.local
pkgm uninstall|rm <pkg> ... remove an installation
pkgm shim|stub <pkg> ... create a shim in <prefix>/bin
pkgm list|ls list what's installed
pkgm outdated list outdated installations
pkgm update|up|upgrade update installations to latest
pkgm pin <pkg>@version ... install pinned to an exact version
pkgm run|x <pkg> [-- args...] run a pkg (works FROM scratch)
flags: -h/--help -v/--version -p/--pin
env: PKGX_DIR bottle store (default: ~/.pkgx)
PKGX_DIST bottle source (default: oci://ghcr.io/go-pkgx/packages, signed)
PKGX_VERIFY verify bottle signatures, fail-closed (default: on)
By default pkgm install lz4.org pulls from the signed OCI registry
oci://ghcr.io/go-pkgx/packages and verifies each bottle's signature before
installing — no env needed. To use the full unsigned upstream pantry instead,
set PKGX_DIST=https://dist.pkgx.dev together with PKGX_VERIFY=0.
The install/uninstall/shim/list/outdated/update/pin command
surface and the ~/.local vs /usr/local prefix logic mirror the reference
pkgm, so it is a drop-in replacement.
~/.pkgx/config.hcl2
Rather than exporting the PKGX_* (and OCI auth) variables every time, set
their defaults declaratively in ~/.pkgx/config.hcl2. It is a small
HCL2 file of top-level attributes; a real
environment variable always overrides a value set here:
# ~/.pkgx/config.hcl2 — defaults for the go-pkgx tools.
# A real environment variable always overrides a value set here.
PKGX_DIST = "oci://ghcr.io/go-pkgx/packages" # signed registry (default)
PKGX_VERIFY = true # fail-closed signature check
# PKGX_DIR = "/opt/pkgx"
# PKGX_PANTRY = "https://raw.githubusercontent.com/pkgxdev/pantry/main/projects"
# OCI_TOKEN = "..." # private-registry credentials
Values may be strings, booleans, or numbers. A missing file is ignored; a malformed one is reported once on stderr and otherwise ignored (the tools fall back to environment variables and built-in defaults).
FROM scratch
run installs a package's full closure (its declared deps plus the implicit
libc/gcc libraries — see automatic closure completion)
and makes the pkgx loader available at /lib/ld-linux, so the binary runs even
on an image with no system libc. Packages that ship a #!/bin/sh wrapper
(git, …) also get the pkgx bash + coreutils — see
wrapper scripts.
FROM scratch
COPY pkgm /pkgm
ENV PKGX_DIR=/pkgx
ENTRYPOINT ["/pkgm"]
$ docker run --rm pkgm-scratch run gnu.org/bash -- --version
GNU bash, version 5.3.0(1)-release (aarch64-unknown-linux-gnu)
A FROM scratch image whose only file is the pkgm binary can install and
run real packages, with no system libc.
automatic closure completion
pkgx bottles link the "implicit system" libraries — libc/libm/libpthread
(glibc) and, for C++ packages, libgcc_s/libstdc++/libatomic (gcc) —
without declaring them, because pkgx assumes a host toolchain. On FROM scratch there is no host. run, and install --from-scratch/-s, read each
bottle's ELF DT_NEEDED (via Go's debug/elf) and pull the bottles that fill
the gap automatically:
- glibc →
gnu.org/glibc libgcc_s/libstdc++→gnu.org/gcc/libstdcxxlibatomic/libgomp→gnu.org/gcc
So a C++ package works with no per-recipe changes:
$ docker run --rm pkgm-scratch run nodejs.org -- --version # pulls glibc + libstdcxx
v22.x.x
root without sudo
A scratch container runs as root, has no sudo, and often no $HOME.
The reference pkgm's root → /usr/local, user → ~/.local, "elevate with
sudo" model does not fit. pkgm treats root as a first-class install mode (no
"use sudo" nagging) and lets you pin the prefix explicitly — ideal in a
Dockerfile:
FROM scratch
COPY pkgm /pkgm
ENV PKGX_DIR=/pkgx
ENV PKGM_PREFIX=/usr # bins land in /usr/bin; no sudo, no HOME needed
ENTRYPOINT ["/pkgm"]
$ docker run --rm -e PKGM_PREFIX=/usr pkgm-scratch install gnu.org/bash
installed gnu.org/bash v5.3.0
linked 2 binaries → /usr/bin
Prefix precedence: --prefix/-P flag → PKGM_PREFIX → root ? /usr/local :
~/.local (falling back to /usr/local when there is no usable $HOME).
wrapper scripts
Some pkgx tools ship as #!/bin/sh wrapper scripts (git, several perl utils)
that set up env and exec the real ELF. On FROM scratch there is no shell — so
pkgm, being a package manager, installs the pkgx gnu.org/bash +
gnu.org/coreutils and points /bin/sh at that real bash (and puts coreutils
like dirname on PATH). It also symlinks the pkgx loader to /lib/ld-linux
(best-effort; a no-op on a normal system) so every bottle ELF — the wrapper's
target and any children — resolves its PT_INTERP natively. Using the real
pkgx bash (rather than reimplementing a shell) means the wrappers get exactly
the set -e semantics they rely on. That is how git runs on scratch.
How it works
- resolve — read
<project>/package.ymlfrom the pkgx pantry and walk the runtimedependencies:graph breadth-first; pick the highestversions.txtentry satisfying each constraint (*,^,~,>=,=). - download — fetch
<project>/<os>/<arch>/v<ver>.tar.{gz,xz}from the bottle source (default: the signed OCI registryoci://ghcr.io/go-pkgx/packages, verifying each bottle's signature;PKGX_DIST=https://dist.pkgx.dev+PKGX_VERIFY=0for the unsigned upstream). - extract — stream straight through gzip/xz + tar into
PKGX_DIR. - link — write env-setting stubs (or run through the loader) so the tools find their sibling bottles' libraries.
FROM scratch conformance
See docs/FROM_SCRATCH.md for the package conformance
matrix, the maintained soname → project fix list, and a proposal to the pkgx
maintainers to publish complete runtime closures.
License
BSD-3-Clause © the go-pkgx/pkgm authors.
Documentation
¶
Overview ¶
Command pkgm is a dependency-free, pure-Go installer for pkgx bottles.
It resolves a package's runtime dependency closure from the pkgx pantry, downloads the bottles — from the signed OCI registry oci://ghcr.io/go-pkgx/packages by default, verifying each bottle's signature (fail-closed) — and installs them, with no runtime dependencies of its own (a single CGO_ENABLED=0 binary that runs on a `FROM scratch` image). Point PKGX_DIST at the unsigned upstream (https://dist.pkgx.dev) with PKGX_VERIFY=0 for the full pantry. It mirrors the reference pkgm CLI so it is a drop-in replacement, and adds a shell-free `run` for scratch images.