# Self-hosted deployment assets This directory is reserved for versioned minddy self-hosting assets. It belongs to the public main repository; no second deployment repository is part of the supported distribution. ## Reference Compose profiles Both profiles use the release-pinned images recorded in `compatibility.json`. Copy `.env.example` to a protected `.env`, replace every placeholder, and use the tagged release directory rather than a development checkout. The guided file deliberately contains only deployment settings; use the root [`../../.env.example`](../../.env.example) for an exhaustive list of optional application integrations. For the normal first installation, prefer `pnpm self-host:install`. It makes the same configuration explicit, refuses to replace an existing environment file, and then starts the profile and bootstrap. Run `pnpm self-host:doctor` afterward for a redacted health and configuration report. The manual commands below remain the advanced/operator-controlled path. ### Managed Supabase Use `compose.managed.yml` when the operator already has a compatible managed or self-hosted Supabase project. It starts minddy and Caddy; it never starts, copies, or changes Supabase services. ```bash export MINDDY_DEPLOY_DIR=/srv/minddy/release/deploy/self-hosted cp "$MINDDY_DEPLOY_DIR/.env.example" "$MINDDY_DEPLOY_DIR/.env" chmod 600 "$MINDDY_DEPLOY_DIR/.env" # Edit $MINDDY_DEPLOY_DIR/.env, including the Supabase Cloud URL and keys. docker compose --env-file "$MINDDY_DEPLOY_DIR/.env" \ -f "$MINDDY_DEPLOY_DIR/compose.managed.yml" up -d ``` The same command starts the scheduler, which drives Numo turns and routines. No additional profile, desktop app, or external execution account is required. Numo does not run code, so neither profile contains a container runner or mounts the Docker socket. ### Official Supabase stack Use `compose.full.yml` only with the upstream revision recorded in the matching compatibility row. `scripts/fetch-official-supabase.mjs` creates an isolated upstream checkout at the pinned commit. It refuses a non-empty target and verifies both upstream files before Compose reads them. That checkout is an operator-owned input, not a copy maintained in this repository. ```bash export MINDDY_RELEASE_DIR=/srv/minddy/release export MINDDY_DEPLOY_DIR="$MINDDY_RELEASE_DIR/deploy/self-hosted" export MINDDY_SUPABASE_DIR=/srv/minddy/supabase node "$MINDDY_RELEASE_DIR/scripts/fetch-official-supabase.mjs" \ --destination "$MINDDY_SUPABASE_DIR" cp "$MINDDY_DEPLOY_DIR/.env.example" "$MINDDY_DEPLOY_DIR/.env" chmod 600 "$MINDDY_DEPLOY_DIR/.env" # Configure the upstream Docker environment and minddy variables in this file. docker compose --env-file "$MINDDY_DEPLOY_DIR/.env" \ -f "$MINDDY_SUPABASE_DIR/docker/docker-compose.yml" \ -f "$MINDDY_DEPLOY_DIR/compose.full.yml" up -d ``` The full profile removes every upstream public host port, including the API gateway and Supavisor. Caddy is the only public service. PostgreSQL binds only to the host loopback on `MINDDY_POSTGRES_BIND_PORT` for bootstrap and maintenance. Upstream PostgreSQL and Storage paths remain persistent exactly as defined by the official distribution; Caddy uses the named `caddy_data` and `caddy_config` volumes for certificates and state. Do not change individual upstream service images or make local edits in `$MINDDY_SUPABASE_DIR`; fetch the next matrix-pinned revision during an upgrade. ## TLS and network boundary For a public deployment, set `MINDDY_HOST` and `MINDDY_SITE_ADDRESS` to the DNS hostname and provide `CADDY_EMAIL`. Caddy then obtains and renews certificates automatically. Ensure ports 80 and 443 reach the host and DNS resolves before starting the profile. For a pre-existing TLS load balancer, bind Caddy to loopback with `MINDDY_HTTP_BIND_ADDRESS=127.0.0.1` and configure that balancer to proxy to it. Both profiles support a server confined to a trusted private network. Set `MINDDY_HOST` to its private IPv4 address and `MINDDY_SITE_ADDRESS=http://`. For the complete profile, also set `SUPABASE_SITE_ADDRESS=http://:8000` and `MINDDY_SUPABASE_HTTP_BIND_ADDRESS=0.0.0.0`. Restrict ports 80 and 8000 to the LAN, keep router ports 80, 443, and 8000 closed, and never expose this HTTP mode to the internet. For public HTTPS, the complete profile also requires `SUPABASE_HOST`; Caddy terminates TLS for it and sends API traffic to the upstream `api-gw` service. For a private full stack, the installer derives the Supabase API address from the server IP and uses port 8000. PostgreSQL, Supavisor, and all other internal service ports are never published by the reference profile. ## Scheduled jobs and checks `scheduler.mjs` mirrors the schedules in [`../../vercel.json`](../../vercel.json) and sends authenticated requests only to the internal `minddy` service. `CRON_SECRET` is generated by the installer and the scheduler starts by default. Releases up to v0.11.0 also shipped an `agent-runner` service. It no longer exists: when upgrading, start the profile with `--remove-orphans` so Compose deletes the retired container, and remove `AGENT_EXECUTION_BACKEND`, `AGENT_RUNNER_URL`, `AGENT_RUNNER_SECRET`, and `AGENT_CONTROL_ORIGIN` from the protected environment. The application joins the `app_egress` network for outbound Supabase, AI provider, and forge traffic. Run the versioned validation before an operator smoke test: ```bash node "$MINDDY_RELEASE_DIR/scripts/validate-self-hosted-compose.mjs" ``` It renders both Compose profiles with safe disposable values and confirms that the pinned Caddy and scheduler images include `linux/amd64` and `linux/arm64`. For a live smoke test, use a disposable Supabase project and a disposable DNS name. The command starts a fresh profile, waits for health checks, proves Caddy serves the app and redirects HTTP to HTTPS, restarts the application and proxy, and starts scheduled jobs: ```bash node -- "$MINDDY_RELEASE_DIR/scripts/smoke-self-hosted-compose.mjs" \ --profile managed \ --env-file "$MINDDY_DEPLOY_DIR/.env" \ --public-url "https://tickets.example.test" ``` After creating a project and attachment, pass an authenticated, stable read URL with `--persistence-url` to compare its response before and after the restart. Inspect `docker compose logs scheduler` after the next schedule boundary for the expected endpoint calls. Record the TLS certificate issuer and the preserved project and attachment before declaring a public instance ready.