Skip to content

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:

sh
docker compose up -d --wait                    # if you use Compose
until nc -z localhost 3000; do sleep 1; done   # or wait on the port yourself

Before 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.

  • exit ends the current hook process. This also applies to the die helper. If a hook exits non-zero, para stops 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 guestReach it asWhat it is
~/.paraspace/stack/<layer>/$PARA_LAYER_DIR for your own layerevery layer in the composed stack, named by layer name and holding its own hooks/, skel/, and commands/
~/.paraspace/host.env$PARA_HOST_ENVyour .env from the host, if that file exists. Workspaces only, never pushed to the image builder
~/.paraspace/envnonegenerated hook environment; do not source it inside hooks (see Hook points)
~/.paraspace/run-hook$PARA_RUN_HOOKhook runner for calling custom hooks
~/.paraspace/helpers$PARA_HELPERSpara'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:

VariableMeaning
PARA_NAMEthis workspace's name
PARA_URLits apex URL, empty unless a bare port routes the apex
PARA_ROUTESthe routes para publishes, space-separated [sub:]port (empty if none)
PARA_DOMAINthe wildcard domain it's served under
PARA_PROJECT_NAMEthe project identity slug
PARA_SHAREDthe shared volume's mount point (/para/shared)
PARA_LAYER_DIRthe layer that owns the running hook or command. A guest path in hooks and a host path in commands
PARA_STACKthe composed layers in stack order, one path per line. Guest paths in the guest and host paths on the host
PARA_RUN_HOOKpara's hook runner (see Hook points)
PARA_HELPERSpara's helper library. Guest hooks and host commands both receive a path valid on their side
PARA_HOOK_CHAINthe 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_ORIGINwhat to clone, and where
PARA_USER, PARA_UID, PARA_GIDthe workspace user's identity
PARA_HOSTNAMEthe host's short hostname, used as the ssh-key label
PARA_GIT_NAME, PARA_GIT_EMAILyour host git identity, for a seeded gitconfig
PARA_CONTRACTthe 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 passWriteRead byLives as long as
setup completion statusa marker file beside the configured filesthe same hook, next upthe configured files
a shell variable (PATH, $BROWSER)/etc/profile.d/<name>.sh, from image-buildevery login shell after it, so later hooks, para sh, your commandsthe image
a workspace-specific valuea file under $HOMEhooks or commands that need the valuethe workspace
a value every workspace sharesa file under $PARA_SHAREDevery workspace of the projectthe 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.

Released under the MIT License.