# FormBuilder Self-hosted form builder for one workspace with Authentik OIDC sign-in, Prisma/Postgres storage, per-form response ACLs, public form links, webhooks, uploads, and an MCP endpoint. ## Portainer Deployment This repo is ready to deploy as a Portainer stack with `docker-compose.yml`. 1. Create a new git repo and push this directory. 2. In Portainer, create a stack from the git repository. 3. Copy `.env.example` to `.env` in the stack environment and fill the values below. 4. Deploy the stack. The app container runs `prisma migrate deploy` before starting the Next.js standalone server. ## Required `.env` ```bash APP_PORT=3080 POSTGRES_PASSWORD=replace-with-a-strong-password AUTH_SECRET=replace-with-openssl-rand-base64-32 AUTH_URL=https://forms.example.com PUBLIC_FORM_URL=https://forms-public.example.com OIDC_ISSUER=https://authentik.example.com/application/o/formbuilder/ OIDC_CLIENT_ID=replace-with-authentik-client-id OIDC_CLIENT_SECRET=replace-with-authentik-client-secret OIDC_PROVIDER_NAME=Authentik AUTH_BOOTSTRAP_ADMINS=you@example.com ``` Optional values are documented in `.env.example` for Redis rate limiting, email, file storage, hCaptcha, and webhook worker auth. ## Reverse Proxy Run the app behind your reverse proxy with HTTP upstream, then terminate TLS at the proxy. For Nginx Proxy Manager or openresty outside this Compose network: - Scheme: `http` - Forward hostname/IP: the Docker host running this stack - Forward port: `${APP_PORT}`; default `3080` - Public URL: set `AUTH_URL` to the final external URL, for example `https://forms.internal.vyntehome.com` If openresty is attached to the same Docker network as this stack, proxy to `http://app:3000` instead of the host-published port. Minimal openresty location: ```nginx location / { proxy_pass http://127.0.0.1:3080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } ``` `502 Bad Gateway` means openresty cannot reach the upstream. First verify the app from the proxy host with `curl http://127.0.0.1:3080/signin` or `curl http://:3080/signin`. ### Separate Builder And Public Forms Domains To build/manage forms at `forms.internal.vyntehome.com` and serve published forms at `forms.vyntehome.com`, point both reverse-proxy hosts to the same app upstream and set: ```bash AUTH_URL=https://forms.internal.vyntehome.com PUBLIC_FORM_URL=https://forms.vyntehome.com ``` Keep the Authentik redirect URI on the internal builder domain: ```text https://forms.internal.vyntehome.com/api/auth/callback/oidc ``` The public proxy host must forward these paths to the app: ```text /f/* /embed.js /_next/* /api/forms/* /api/files/* ``` Anyone with the link can respond to a published form at `forms.vyntehome.com` without signing in. Form editing and response management remain authenticated on the internal builder domain. ## Authentik Setup Create an OAuth2/OpenID provider in Authentik: - Provider type: OAuth2/OpenID - Client type: Confidential - Redirect URI: `${AUTH_URL}/api/auth/callback/oidc` - Scopes: `openid`, `profile`, `email` - Issuer mode: use the provider's OpenID Configuration Issuer URL Then create an Authentik application and bind it to that provider. Put the issuer, client ID, and client secret in `.env`. The first successful signer becomes an admin. Any emails listed in `AUTH_BOOTSTRAP_ADMINS` are also promoted on first sign-in. ## Persistent Data The compose stack creates two named volumes: - `postgres_data`: bundled Postgres database - `uploads`: local uploaded files mounted at `/app/uploads` For multi-instance deployments, set `RATE_LIMIT_DRIVER=redis` and provide `REDIS_URL`. For durable object storage outside the app container, configure the S3 values in `.env.example`. ## Useful Commands ```bash npm ci npm test npm run build docker compose up --build ``` ## MCP Endpoint The MCP endpoint is available at: ```text POST /api/mcp ``` Create a token in `/app/account`, then send requests with: ```text Authorization: Bearer fb_xxxxxxxxxxxxxxxx ``` ## Stack Next.js 15 App Router, React 19, Auth.js v5, Authentik OIDC, Prisma, Postgres, Tailwind, and Docker Compose.