Skip to main content
These are the failures the local loop and a self-hosted install hit most often.

Ports and the container runtime

  • Container runtime unavailable. pnpm infra:up exits when docker is missing or the daemon is stopped. Start the runtime and run pnpm infra:up again.
  • Port 5432 is taken. Another PostgreSQL is listening on 127.0.0.1:5432. Stop it, or change the host port in compose.yaml and DATABASE_URL together.
  • Port 3900 is taken. Another process is listening on 127.0.0.1:3900. Stop it, or change the Garage host port in compose.yaml and S3_ENDPOINT together.
  • Port 3000 or 3001 is taken. Stop the other process and start again. Local development uses http://localhost:3000 for the web app and http://localhost:3001 for the API.
  • pnpm e2e reports a taken port. That command uses 127.0.0.1:5433 for PostgreSQL, 127.0.0.1:3910 for Garage, 127.0.0.1:3100 for the web app, and 127.0.0.1:3101 for the API.

Startup and storage

  • The API stops on BETTER_AUTH_SECRET. The value is missing or shorter than 32 characters. Set it in apps/api/.env, then start the API again.
  • The API stops on EMAIL_TRANSPORT. The value is not log, smtp, or brevo. Unset uses log.
  • The API stops on EMAIL_FROM_ADDRESS or EMAIL_FROM_NAME. The from address is not an email address, or a set from name is longer than 70 characters or more than one line. An empty address uses support@factrove.app. An empty name uses Factrove.
  • The API stops on SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASSWORD, or SMTP_REQUIRE_TLS. Those checks run when EMAIL_TRANSPORT is smtp. Set host, port, and SMTP_SECURE (true or false). Set SMTP_USER and SMTP_PASSWORD together, or leave both empty. SMTP_REQUIRE_TLS is optional: unset requires STARTTLS when SMTP_SECURE is false. false allows plaintext. An empty SMTP block is valid when the transport is log.
  • The API stops on BREVO_API_KEY. That key is required only when EMAIL_TRANSPORT is brevo. The API does not call GET /v3/account.
  • The invite is only in the API log. EMAIL_TRANSPORT is unset or log, so the log says EMAIL_TRANSPORT is log. Invite email was not sent. Set smtp or brevo in apps/api/.env and create an account with a new address. The recipe takes that address as its first argument. See Invite email.
  • Sign-up returns 200 and the log says the invite email failed. The account exists. The line starts with Invite email failed for. Check the SMTP server or the Brevo key for the transport you selected. The from address defaults to support@factrove.app. A second sign-up for the same address is 422 and does not send again.
  • Shutdown logs Invite email shutdown left N send(s) unfinished. The API waited 10 seconds for queued and in-flight invites, then exited. Those accounts exist. A second sign-up for the same address is 422 and does not send again. pnpm dev and Compose give the API 15 seconds before a force stop.
  • The log says Invite email skipped for. One hundred invites were already waiting. The account exists. That invite was not sent.
  • Upload returns STORAGE_UNAVAILABLE. The API could not write the object, and the failed upload leaves no artifact row. For Garage, run pnpm infra:up and match S3_ENDPOINT, S3_BUCKET, S3_REGION, S3_FORCE_PATH_STYLE, and the access key in apps/api/.env to compose.yaml. For AWS, set S3_BUCKET and S3_REGION, remove S3_ENDPOINT, S3_FORCE_PATH_STYLE, and the Garage key pair, and use real credentials or the AWS credential chain. S3_BUCKET=your-bucket is an old placeholder. See Object storage.
  • The CLI reports NETWORK_ERROR. The API origin is wrong or the API is not running. Precedence is --endpoint-url or --api-url, then FACTROVE_API_URL, then FACTROVE_ENDPOINT_URL, then the selected profile’s api_url or endpoint_url, then a URL from apps/cli/.env when you use pnpm factrove, then https://api.factrove.app. An explicit --profile uses that profile’s key and ignores FACTROVE_API_KEY. The published commands are factrove and ftrv. pnpm factrove is the checkout launcher.
  • The library says the API is unavailable. The web app is using a different origin from the running API. Set NEXT_PUBLIC_API_URL in apps/api/.env or the environment, then restart pnpm dev. A production bundle from pnpm build keeps the origin from the build, so run pnpm build again after changing it. On the Docker web image, set BROWSER_API_URL to the origin the browser uses and INTERNAL_API_URL to the origin the Next.js server can reach. Those two are read at request time.
  • Docker Compose upload returns STORAGE_UNAVAILABLE. The API container reaches Garage at http://garage:3900 with S3_ALLOW_INSECURE_HTTP=true. pnpm dev does not use that flag. It keeps http://127.0.0.1:3900.
  • docker compose up starts only PostgreSQL and Garage. The command without -f docker-compose.yml uses compose.yaml. The self-host stack is docker compose --env-file .env.docker -f docker-compose.yml up --build -d.
  • Garage exits during the self-host start. The log says Invalid RPC secret key: expected 32 bytes of random hex. GARAGE_RPC_SECRET must be 64 hex characters. openssl rand -hex 32 prints that. The placeholder in .env.docker.example is not a valid secret.
  • The API exits and the database URL looks truncated. POSTGRES_PASSWORD is copied into DATABASE_URL without encoding. @, :, /, or # in that password breaks the URL. Use the hex output of openssl rand -hex 32.
  • Self-host ports 3000 or 3001 are taken. WEB_PORT and API_PORT default to those ports, on 127.0.0.1. pnpm dev uses the same pair. Stop the other process, or change WEB_PORT, API_PORT, and the public origins together. See Configure.

Cookies and origins

A cookie-authenticated write (POST, PUT, DELETE, and other methods besides GET, HEAD, and OPTIONS) needs Origin or Referer to be WEB_ORIGIN or PUBLIC_BASE_URL. A missing or different origin is 403 FORBIDDEN with message This request origin is not allowed. Bearer requests skip that check. Do not send Authorization on API-key create, list, or delete. localhost and 127.0.0.1 are different hosts. A cookie domain cannot cover either. When the web app and API use different public hosts, set AUTH_COOKIE_DOMAIN to a parent of both. On an https API origin the session cookie is named __Secure-better-auth.session_token. On http it is better-auth.session_token. Use a cookie jar rather than typing the name. The recipe is in API overview.

Profiles

--profile, then FACTROVE_PROFILE, then default selects the profile. pnpm factrove can fill an unset FACTROVE_PROFILE from apps/cli/.env. node apps/cli/dist/main.js does not read that file. Profile files live in ~/.factrove unless FACTROVE_CONFIG_DIR, FACTROVE_SHARED_CREDENTIALS_FILE, or FACTROVE_CONFIG_FILE points elsewhere. Revoke clears the password. The next share is unprotected unless you pass the password again:
--password needs a terminal. Rotating SHARE_TOKEN_KEY, or BETTER_AUTH_SECRET when that key is unset, makes existing public URLs return 404. Share again and supply the password if the link should stay protected. See Lockout recovery.

Content tickets

A signed-in viewer loads HTML through a short access query on the content URL. That grant lasts five minutes. Refresh the viewer to mint another one. A password-protected public link uses a separate ticket query that lasts 60 seconds and is honored at most three times per API process. Refresh the share page to mint another ticket. Reverse proxies should not log either query.