Hooks
Hooks define workspace provisioning and startup. A project's own hooks live in .paraspace/layers/project/hooks/. For each hook, para runs every layer's implementation in stack order.
The three official hooks
para runs the following hooks and para init creates stubs for them. You can also define your own names. Each plain hook runs between automatic :before and :after points. See Hook points.
provision
Use provision to prepare the shared volume, clone the repository, configure authentication, and render .env before services start.
The hook must be idempotent because every para up runs it again. It can prompt when a tty is available, for example to authorize an SSH key or sign in with gh.
para up waits for guest DNS to resolve PARA_READY_HOST before running hooks. The default is paraspace.dev; set another hostname if needed.
boot
Start application services and return zero only when every routed service is listening:
docker compose up -d --wait # if you use Compose
until nc -z localhost 3000; do sleep 1; done # or wait on the port yourselfBefore running hooks, para waits for the container agent and for DNS to resolve $PARA_READY_HOST. The boot hook must check service readiness itself; para uses its exit status to determine whether startup succeeded.
image-build
Install packages, create the workspace user, and perform other setup shared by all workspaces. para image build runs this hook in a temporary container and uses the result as the base image.
The hook runs as root, without a workspace $HOME. The workspace user may not exist yet. The stack is available at /opt/.paraspace, and $PARA_LAYER_DIR/skel provides the layer's seed files. $PARA_HOST_ENV is available only in workspaces, so read it during provision.
Image builds have no tty or stdin. Disable package-manager prompts, for example with -y.
See The image contract to learn what your final image must contain.
A hook is a process
para runs each matching hook file in a separate bash process:
Write hooks in Bash. They do not need executable permissions.
Source the helper library through
$PARA_HELPERS:sh# shellcheck source=/dev/null . "$PARA_HELPERS"There are no arguments. Hooks read configuration from environment variables and files.
exitends the current hook process. This also applies to thediehelper. If a hook exits non-zero,parastops running hooks and returns that status.
Layer files in the workspace
The composed stack becomes the workspace's ~/.paraspace/stack/. Use $PARA_LAYER_DIR to access your layer without depending on the directory layout.
| In the guest | Reach it as | What it is |
|---|---|---|
~/.paraspace/stack/<layer>/ | $PARA_LAYER_DIR for your own layer | every layer in the composed stack, named by layer name and holding its own hooks/, skel/, and commands/ |
~/.paraspace/host.env | $PARA_HOST_ENV | your .env from the host, if that file exists. Workspaces only, never pushed to the image builder |
~/.paraspace/env | none | generated hook environment; do not source it inside hooks (see Hook points) |
~/.paraspace/run-hook | $PARA_RUN_HOOK | hook runner for calling custom hooks |
~/.paraspace/helpers | $PARA_HELPERS | para's output and interactivity helpers, replaced by para on every push |
Files come from your host checkout, not the clone, and the composed stack is pushed fresh on every up, replacing what was there. Editing a hook takes effect on the next para up without re-cloning; a layer dropped from the stack disappears from the guest too, and a hook can seed from $PARA_LAYER_DIR/skel before the clone exists.
para sources .paraspace/env on the host, and the file itself is never pushed. Every PARA_* variable it declares reaches the guest as an export line in ~/.paraspace/env, so a secret in a PARA_* variable reaches every workspace. A non-PARA_* variable stays on the host.
The environment para injects
para forwards every PARA_* variable in scope: your user config, everything your env file sets, and the per-workspace values para computes. This includes custom settings such as PARA_FOO. Guest hooks do not receive the host-only paths PARA_BIN, PARA_PROJECT_DIR, PARA_CONFIG, PARA_CONFIG_DIR, or PARA_STATE_DIR.
Use scalar values. Arrays are not preserved when forwarded to hooks. For lists, pass a delimited string and split it in the hook. The supported variables are:
| Variable | Meaning |
|---|---|
PARA_NAME | this workspace's name |
PARA_URL | its apex URL, empty unless a bare port routes the apex |
PARA_ROUTES | the routes para publishes, space-separated [sub:]port (empty if none) |
PARA_DOMAIN | the wildcard domain it's served under |
PARA_PROJECT_NAME | the project identity slug |
PARA_SHARED | the shared volume's mount point (/para/shared) |
PARA_LAYER_DIR | the layer that owns the running hook or command. A guest path in hooks and a host path in commands |
PARA_STACK | the composed layers in stack order, one path per line. Guest paths in the guest and host paths on the host |
PARA_RUN_HOOK | para's hook runner (see Hook points) |
PARA_HELPERS | para's helper library. Guest hooks and host commands both receive a path valid on their side |
PARA_HOOK_CHAIN | the current sequence of nested hook calls, used in error reports. para updates it for each call; hooks may read it but must not change it |
PARA_CLONE_DIR, PARA_CLONE_BRANCH, PARA_ORIGIN | what to clone, and where |
PARA_USER, PARA_UID, PARA_GID | the workspace user's identity |
PARA_HOSTNAME | the host's short hostname, used as the ssh-key label |
PARA_GIT_NAME, PARA_GIT_EMAIL | your host git identity, for a seeded gitconfig |
PARA_CONTRACT | the contract your env file targets. para checks for a mismatch before running hooks |
In Void image hooks, use xbps_install <package>... from $PARA_HELPERS to install missing packages.
Set PARA_NONINTERACTIVE=1 to disable interactive behavior. It is forwarded to hooks so they can skip prompts too.
para's own internals may also appear in the environment; only the variables above are the versioned contract.
Share data between hooks
An export inside a hook does not affect later hooks, shells, or project commands. Store shared values in files according to how long they should last.
| To pass | Write | Read by | Lives as long as |
|---|---|---|---|
| setup completion status | a marker file beside the configured files | the same hook, next up | the configured files |
a shell variable (PATH, $BROWSER) | /etc/profile.d/<name>.sh, from image-build | every login shell after it, so later hooks, para sh, your commands | the image |
| a workspace-specific value | a file under $HOME | hooks or commands that need the value | the workspace |
| a value every workspace shares | a file under $PARA_SHARED | every workspace of the project | the shared volume |
Use layer-specific paths, such as /etc/profile.d/dotfiles.sh or $PARA_SHARED/dotfiles/, to avoid file conflicts between hooks.
Hooks within the same invocation inherit the environment set before the first hook ran. A /etc/profile.d file written during provision takes effect in the next invocation (boot, para sh, or the next up), not in the next layer's provision hook. Files are available to later hooks as soon as they are written.