Skip to content

Troubleshooting

Start with para doctor

Everything para knows about how a machine can be misconfigured lives in one command, so the commands you use every day don't carry it:

$ para doctor

config
    user config        /home/you/.config/para/config
    state              /home/you/.local/state/para
    project dir        /home/you/src/myapp
    PARA_PROJECT_NAME  myapp
    PARA_IMAGE_NAME    myapp
    PARA_DOMAIN        paraspace.dev
    PARA_ROUTES        8080
    PARA_POOL          default
    PARA_VOLUME        para-home-myapp
    Caddy port         8443

host
  ✓ caddy present
  ✓ para Caddy running on :443
  ✓ *.paraspace.dev resolves to 127.0.0.1

incus
  ✓ incus 6.22
  ✓ pool 'default' (dir)
  ✓ bridge 'incusbr0' up

project
  ✓ env targets contract 1
  ✓ layer node_modules/paraspace/layers/base/void
  ✓ layer node_modules/paraspace/layers/git
  ✓ layer .paraspace/layers/project
  ✓ routes: 8080
  ✓ image 'myapp' exists

passed, ! is advice, and is a failure that exits non-zero. The config block is also the answer to "what did para actually resolve", so check it whenever a setting doesn't seem to be taking effect.

The host

Your Incus is too old

✗ incus 6.2 cannot select device columns, which is how para reads workspace state. Upgrade to 6.22 or newer (Ubuntu's repos ship 6.2)

para has no registry. It asks Incus for each workspace's project, routes and IP as query columns, which needs Incus ≥ 6.22. Distro repos lag, so install from the Incus package repositories.

Containers won't start at all

✗ cgroup-v1 mounted inside /sys/fs/cgroup (…). No container will start until you unmount it: sudo umount -l …

A named cgroup-v1 hierarchy mounted under /sys/fs/cgroup makes LXC fail every container start with a cryptic Failed to create cgroup at_mnt. Unmount it, and doctor prints the exact path:

sh
sudo umount -l /sys/fs/cgroup/<name>

A workspace dies mid-boot on the shared volume

✗ this kernel cannot do idmapped mounts … ! OpenZFS 2.1 is older than 2.2 …

The shared volume attaches to many unprivileged containers at once, which needs either shiftfs or idmapped mounts. Without them a workspace fails with Required idmapping abilities not available. Kernel ≥ 5.12 (≥ 5.15 on btrfs), and OpenZFS ≥ 2.2 if the pool is on ZFS.

Everything inside the workspace is slow

! pool 'default' is btrfs-backed, so nested Docker falls back to vfs

Nested Docker can't use overlayfs on a btrfs or ZFS-backed Incus pool, so it falls back to the vfs driver, which copies the whole filesystem per layer. Put para on a dir pool over ext4/xfs:

sh
incus storage create para-dir dir source=/path/on/ext4

Then set PARA_POOL in your user config.

The workspace is up but the URL doesn't load

✗ *.paraspace.dev does not resolve to 127.0.0.1, so workspace URLs will not load (docs/urls.md)

The wildcard has to point at your machine. The default paraspace.dev already does; a custom PARA_DOMAIN needs a wildcard record of your own. See Workspace URLs.

If DNS is fine, check Caddy is actually up (para caddy status) and that the browser trusts its CA. A first-run caddy trust covers most browsers, and Workspace URLs covers the rest.

Caddy can't bind :443

✗ caddy cannot bind :443 unprivileged. Run sudo setcap …

Non-root can't bind below 1024 on Linux. Grant the capability (re-apply after every caddy upgrade), or stay on the default :8443:

sh
sudo setcap cap_net_bind_service=+ep "$(readlink -f "$(command -v caddy)")"

A workspace has no outbound network access

ping is not a connectivity test inside a workspace. Unprivileged containers cannot open raw sockets, so it fails with ping: socket: Operation not permitted and missing cap_net_raw+p capability or setuid? even when networking works.

Test DNS and outbound TCP from the workspace instead:

sh
getent hosts github.com
curl -m5 -sI https://github.com

Docker on the host commonly blocks the incusbr0 bridge. Confirm it with:

sh
sudo iptables -S FORWARD | head -3

If it shows -P FORWARD DROP and has a DOCKER-USER chain, allow the bridge:

sh
sudo iptables -I DOCKER-USER -i incusbr0 -j ACCEPT
sudo iptables -I DOCKER-USER -o incusbr0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

Persist these rules because Docker rebuilds its chains on restart. For hosts using ufw, run sudo ufw route allow in on incusbr0 and sudo ufw route allow out on incusbr0; with firewalld, add incusbr0 to the trusted zone. If a literal-IP curl works but getent fails, the same firewall is blocking dnsmasq on port 53. See Prevent connectivity issues with Incus and Docker.

macOS: incus daemon unreachable

Incus runs inside a Colima VM there. colima start --runtime incus, then re-run.

The project

Stack layers are missing on disk

stack layers missing on disk: node_modules/paraspace/layers/base/void Run 'npm install' if these live under node_modules/, else fix .paraspace/stack.

Fresh clones can be missing layers that live under node_modules. Install the project dependencies:

sh
npm install

If the layer path is just wrong, fix its line in .paraspace/stack. See Layers.

no image 'myapp'. Build it with: para image build

The base image is per-project and per-arch, and it isn't built for you. Run para image build, which takes several minutes the first time. See The image contract.

Permission denied (publickey) during the first up

The machine's para key isn't authorized at your git host yet. para up is idempotent, so authorize it and re-run. See Shared authentication.

up succeeds but the URL returns 502

Caddy is proxying to a port nothing is listening on. Almost always a boot hook that returned zero before its services were actually up. The readiness contract requires that it return only once every routed service is listening (docker compose up -d --wait does that for a Compose project; anything else needs its own wait). Check from inside:

sh
para sh <name> -c 'ss -ltnp'

this project targets para contract N, but this para provides 1

A globally-updated para met a project pinned to an older contract, and refused rather than misbehaving. See Contract versioning.

para sh -c fails on a minimal image

Running para sh <ws> -c '<cmd>' from a terminal uses su --pty, which is util-linux; busybox's su (plain Alpine) doesn't have it. That's an image requirement. A bare para sh, and -c with its output piped or redirected, use plain su - and work anywhere.

Still stuck

incus info --show-log para-<name> shows why a container refused to start, and the generated Caddyfile is at ${XDG_STATE_HOME:-~/.local/state}/para/Caddyfile if you want to see exactly what para asked Caddy to serve.

Released under the MIT License.