Self-hosted Monei.AI with a host-owned Supabase Docker stack
8.9K
Self-hosted Monei.AI for Docker Compose.
The image is published as:
moneiai/app:latest
The application, migration runner, and MCP images are published for Linux AMD64
and ARM64. The application image contains the Monei.AI frontend, Edge Functions,
and a small shell-less bootstrap binary. Database migrations run from the
separate moneiai/migrations:latest image, which embeds the SQL and does not
mount migration files into the stack directory. Supabase is intentionally not
bundled in either image. The setup downloads the pinned Supabase Docker
distribution into your own stack directory, so its source, containers,
database, storage, and volumes remain under your control. Supabase Studio is
published through a local authentication proxy and protected with the
generated dashboard credentials.
The publish workflow also builds a standalone non-root moneiai/agent:latest
image for AMD64 and ARM64. An opt-in Compose profile wires it to the existing
PostgreSQL instance; it does not run or expose an agent by default. GET
/healthz checks liveness only, not database access, model configuration, or
readiness for chat. See the Agent guide for product
behavior, architecture, configuration, and operations.
Production deployment remains a separate reviewed step.
The Agent profile also starts the non-root moneiai/compute:latest image. It has
no network or public port and receives no Agent/database/provider credentials.
A private named volume carries only its Unix IPC socket; temporary computation
files remain in memory, not in that volume. The image requires Linux seccomp
(including the Linux VM used by Docker Desktop), and fails startup without
verified internet-socket denial. Rootless Docker remains supported without host
UID/GID assumptions. MONEI_COMPUTE_IMAGE selects the image;
AGENT_CODE_MODE_ENABLED=false disables code-mode registration. Updates must
pull both Agent and computation images before recreating the Agent profile.
Do not mount host directories or publish the IPC service. Scratch files are
discarded rather than retained during updates; private verified conversation
results remain in PostgreSQL. See isolated computation
for capabilities, resource limits, cancellation and residual risks.
Requirements:
curl, tar, and shasum on the hostThe commands below use portable POSIX shell syntax and work from Linux bash or macOS Terminal/zsh. Docker Desktop must be running on macOS before starting the stack.
Run this from an empty directory or a directory reserved for the local stack:
set -eu
MONEI_VERSION="latest"
SUPABASE_VERSION="v1.26.05"
SUPABASE_SHA256="4502c8db7afcc2ff0ae120003e446b4f8084e7f6b83ce17fe2e9f3b426e69742"
STACK_DIR="${MONEI_STACK_DIR:-$PWD/monei-local}"
SUPABASE_ARCHIVE_URL="https://github.com/supabase/supabase/archive/refs/tags/${SUPABASE_VERSION}.tar.gz"
SUPABASE_ARCHIVE_FILE="$(mktemp "${TMPDIR:-/tmp}/monei-supabase.XXXXXX")"
trap 'rm -f "$SUPABASE_ARCHIVE_FILE"' EXIT
mkdir -p "$STACK_DIR/supabase"
curl -fsSL "$SUPABASE_ARCHIVE_URL" -o "$SUPABASE_ARCHIVE_FILE"
printf '%s %s\n' "$SUPABASE_SHA256" "$SUPABASE_ARCHIVE_FILE" \
| shasum -a 256 -c -
tar -xzf "$SUPABASE_ARCHIVE_FILE" \
--strip-components=2 -C "$STACK_DIR/supabase" \
"supabase-${SUPABASE_VERSION#v}/docker"
docker run --rm \
-v "$STACK_DIR:/data" \
-e MONEI_DATA_DIR=/data \
-e MONEI_OWNER_UID="$(id -u)" -e MONEI_OWNER_GID="$(id -g)" \
"moneiai/app:${MONEI_VERSION}" init
cd "$STACK_DIR"
docker compose --env-file .env up -d
The command is safe to run again. Existing values in .env are preserved.
The shared root environment example documents development
and Docker inputs, generated secrets, and container-only wiring. Optionally copy
that example into a new stack as .env before init to customize settings.
Blank generated values are filled during initialization; local-only client
settings are not forwarded by Compose. Never replace an initialized stack's
private .env with the example or a configured developer .env.
Ownership changes apply only to launcher-generated files; existing database and
storage volumes retain their service-managed ownership. When upgrading the
Supabase distribution, review the release and update SUPABASE_VERSION and
SUPABASE_SHA256 together before extracting it.
The downloaded Supabase files are reused, and the image copies the current
Edge Functions, Compose overlay, and Supabase config into the stack directory.
Compose pulls moneiai/migrations:latest and moneiai/mcp:latest when they
are not already present. Run docker compose pull before an update so existing
local latest images are refreshed.
Rootless Docker and Docker installations using a remapped user namespace map
container IDs to subordinate host IDs. The launcher detects this mapping and
does not apply an explicit host UID/GID chown, because id -u inside the
container is not the host user's UID. Files are created by container root, which
maps back to the invoking host user, while .env remains mode 0600. Keep the
stack directory private and do not mix sudo docker with a rootless Docker
installation.
If a stack initialized by an older image has inaccessible generated files, first
back up the private .env, database, storage, and named volumes. Rerun init
with the current application image and the same stack directory. If the Docker
user-namespace mapping still prevents access, repair ownership according to that
Docker installation or restore into a new private stack directory. Never apply a
recursive world-readable or world-writable mode: the generated .env contains
production secrets and must remain 0600.
macOS Docker Desktop normally presents bind-mounted files using the host user, so this workaround is usually unnecessary on macOS.
| Service | URL | Purpose |
|---|---|---|
| Monei.AI | http://localhost:8080 | Web application |
| Monei MCP | http://localhost:3001/mcp | OAuth-protected AI agent tools |
| Supabase API | http://localhost:8000 | Auth, REST, Realtime, Storage, and Edge Functions gateway |
| Supabase Studio | http://localhost:3000 (loopback only) | Database and project administration (login required) |
PostgreSQL and the connection pooler are kept on the internal Compose network.
They are not published to the host by the Monei overlay. The optional Agent also
has no published port. Enabling the agent profile starts its required
authenticated Redis dependency on a private network, with AOF persistence in
the agent-redis-data named volume and a no-eviction policy. The separate
agent-redis profile can start that dependency alone. PostgreSQL remains
authoritative for checkpoints, journal, ownership, and cancellation; Redis
provides native event transport/cache and wakeups, not execution authority.
Do not reuse Chatwoot Redis.
The pinned Supabase Vector container forwards Docker host logs into Analytics.
It is in the optional container-logs profile: the default
DOCKER_SOCKET_LOCATION=/dev/null deliberately does not grant containers
access to the host Docker daemon. Core application, Auth, API, and Studio
remain available without container-log forwarding. If you deliberately enable
that profile, configure a readable host Docker socket at
DOCKER_SOCKET_LOCATION first and review its daemon-level security exposure;
the path differs on rootless Docker and Docker Desktop. Otherwise use
docker compose logs for service diagnostics. Do not publish the Docker socket
or enable this profile merely to satisfy readiness checks.
The MCP service uses Supabase OAuth 2.1 dynamic client registration and opens
its consent screen through the Monei application at
http://localhost:8080/mcp/consent.
Billing maintenance reuses the stack's SMTP_HOST, SMTP_PORT, SMTP_USER,
SMTP_PASS, SMTP_ADMIN_EMAIL and SMTP_SENDER_NAME. Set these in the private
stack .env, rerun moneiai/app:latest init, then recreate the services with
docker compose --env-file .env up -d --wait. Existing values are preserved.
SMTP_TLS_MODE=auto uses verified TLS on port 465 and requires STARTTLS otherwise;
tls and starttls select these modes explicitly. An unauthenticated private
development relay can use none; that mode rejects SMTP authentication credentials.
Initialization generates and preserves BILLING_MAIL_ENCRYPTION_KEY, containing
32 random bytes encoded as unpadded base64url. It and SMTP credentials reach only
Edge Functions, never /config.js. Keep this key when updating the stack; replacing
it requires draining or invalidating pending encrypted mail first. Notifications
use the existing scheduler and Supabase functions runtime, with no new container
or launcher command. Failed configuration/submission appears in admin Operations;
submission status reflects SMTP acceptance, not mailbox delivery. See the
administration domain for privacy and
recovery behavior.
Billing invitations use this same encrypted mail configuration for the invitation and a separate mailbox code. Auto-confirmed signup does not bypass mailbox proof. Preparation failures appear on the invitation list; correct configuration and resend to create a new link. Existing links are invalidated on resend. See benefits and invitations.
AWESOMEAPI_API_KEY is an optional server-side key for investment FX quotes.
Set it in the stack's private .env, rerun moneiai/app:latest init, and
recreate the stack with docker compose --env-file .env up -d --wait.
Initialization preserves existing values. The key is passed only to Edge
Functions; an empty value uses unauthenticated provider requests.
The hourly FX job and investment queue worker use the internal functions URL
and cron secret stored in Vault by the stack configuration.
Studio is protected by the authentication proxy on port 3000. The username
and password are generated during the first init command and stored in the
stack directory's .env file. They are preserved when you rerun init or
update the images.
From the stack directory, display the credentials with:
STACK_DIR="/absolute/path/to/monei-stack"
cd "$STACK_DIR"
grep -E '^DASHBOARD_(USERNAME|PASSWORD)=' .env
Open http://localhost:3000 in a browser and enter those values on the Studio
login page. If you changed STUDIO_PORT, use that port instead, for example
http://localhost:3500.
The same credentials also protect Studio when it is accessed through the
Supabase gateway at http://localhost:8000. The port 3000 login page is the
recommended local entry point because it provides a normal browser login form.
Keep .env private: DASHBOARD_PASSWORD is an administrative credential for
your local Supabase project. The Studio proxy binds only to 127.0.0.1; for
remote administration, use an SSH tunnel rather than publishing port 3000.
Restrict the Studio route on any public Supabase gateway at the TLS ingress as
well; the gateway's own dashboard authentication does not make it a public
service.
The stack includes the application plus the current pinned Supabase service set; optional profiles add Agent, Redis, support, or container-log workloads. Because that topology and its upstream images change, do not use a checked-in container count or point-in-time image/memory measurement for capacity planning. Inspect the exact release before installation:
docker compose --env-file .env config --services
docker compose --env-file .env images
docker system df -v
docker stats --no-stream
Size disk for both current and replacement image layers during updates, plus
PostgreSQL, Storage, logs, backups, and any optional support attachments. Monitor
the running workload and increase CPU, memory, or disk before sustained pressure
reaches the database. Do not run docker system prune or remove named volumes as
routine cleanup; deleting volumes can destroy application data.
The installation consists of the stack directory plus Docker named volumes. The
stack directory contains the generated .env, downloaded Supabase Docker files,
Edge Functions, and Compose configuration; Docker's volume store contains the
database and storage data. Back up both boundaries. Stop and restart without
losing data:
STACK_DIR="/absolute/path/to/monei-stack"
cd "$STACK_DIR"
docker compose stop
docker compose start
Remove containers while retaining named volumes:
docker compose down
Reset the local installation, including database and storage volumes:
docker compose down -v
Only use down -v when a complete local reset is intended.
Pass overrides on the first init command. They are written to the persistent
.env file and are reused on later starts. Put sensitive overrides in a private
file outside the repository and restrict it to the operator:
# /private/path/monei-init.env (mode 0600)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_SECRET=your-google-client-secret
MONEI_PUBLIC_URL=https://app.example.com
SUPABASE_PUBLIC_URL=https://api.example.com
chmod 600 /private/path/monei-init.env
docker run --rm \
-v "$STACK_DIR:/data" \
-e MONEI_DATA_DIR=/data \
--env-file /private/path/monei-init.env \
"moneiai/app:latest" init
Remove the temporary override file when it is no longer needed. Do not pass secrets directly on a command line or store this file in the stack repository.
Important configuration values include:
GOOGLE_CLIENT_ID and GOOGLE_SECRET enable Google authentication together.MONEI_PUBLIC_URL controls the browser-facing application URL.SUPABASE_PUBLIC_URL controls the browser-facing API URL.SITE_URL and ADDITIONAL_REDIRECT_URLS control Auth redirect validation.
SITE_URL is also the application origin used for the OpenFinance callback. Admin
requests default to that origin; deployments with additional frontends can
configure ADMIN_ALLOWED_ORIGINS as described in administration.MONEI_HTTP_PORT, KONG_HTTP_PORT, and STUDIO_PORT change host ports.DASHBOARD_USERNAME and DASHBOARD_PASSWORD protect Studio access through
the login page at http://localhost:3000.MONEI_SENTRY_DSN; see the Sentry guide for scope,
data handling, and setup.The generated .env contains database, JWT, storage, and service secrets.
Keep it private and do not commit it.
The agent Compose profile starts the separate non-root Agent image after the
migration job and one-shot restricted-login provisioner complete. monei init
generates and preserves the Agent database password, internal MCP secret, run
recovery key, provider configuration, and required Redis settings in the private
stack .env. The runtime receives only the restricted monei_agent_runtime
database URL.
Configure the server-side settings described in the Agent guide, then start the profile:
docker compose --profile agent --env-file .env up -d --wait
The agent profile includes authenticated private Redis for native delivery
and cache. PostgreSQL remains authoritative. An external authenticated Redis
URL can replace the client destination; new execution requires Redis readiness.
The Agent has no published host port and is reached through the application proxy. Preserve authorization and workspace headers through an external TLS proxy, disable response buffering for streams, and do not expose Mastra Studio, PostgreSQL, Supavisor, Redis, or the internal Agent MCP route. The MCP service itself is published for the public protocol, so enforce network policy at the host or external proxy when private-path isolation is required.
Use docs/agent.md as the sole source for environment variables, owner/member enablement, provider identity, readiness, diagnostics, updates, and rollback.
Chatwoot is optional and disabled in the generated stack unless configured. The canonical support guide owns product behavior, identity, configuration, local development, managed deployment, and operations.
For the packaged self-hosted distribution, the Chatwoot database must use an
image that includes pgvector. After monei init, set
CHATWOOT_POSTGRES_IMAGE in the private stack .env to the value maintained in
the root environment example before starting the optional
support profile.
cd "$STACK_DIR"
docker compose --profile support --env-file .env up -d
The profile owns separate Chatwoot PostgreSQL, Redis, and attachment volumes. Do
not start it when CHATWOOT_BASE_URL points to an external installation.
The current Chatwoot containers also load the stack-wide .env; their data and
network are separate, but they remain inside the same trusted process-secret
boundary. See the canonical support guide.
Keep Rails loopback-bound behind HTTPS, and never publish its database or Redis.
The executable topology and launcher defaults remain authoritative in
docker/compose.yaml and docker/launcher/main.go.
Provider credentials remain server-side and are propagated to Edge Functions by the launcher. Configure public callback origins before exposing a real connection flow. The canonical architecture, environment variables, local HTTPS workflow, webhook setup, verification, and operating behavior are documented in the Open Finance guide.
Set both Google values before initialization. Register this callback with the Google OAuth client:
http://localhost:8000/auth/v1/callback
For a public deployment, use the HTTPS API domain instead:
https://api.example.com/auth/v1/callback
The Google provider remains disabled when either credential is missing.
For a public deployment, place a TLS reverse proxy in front of the Monei,
Supabase API, and public MCP ports. Set MONEI_PUBLIC_URL,
SUPABASE_PUBLIC_URL, SITE_URL, ADDITIONAL_REDIRECT_URLS,
MCP_CANONICAL_URL, MCP_RESOURCE_METADATA_URL, and
MCP_AUTHORIZATION_SERVER to their public HTTPS URLs before initialization.
Allow only the intended public MCP and OAuth routes, preserve authorization and
streaming semantics, and keep the internal /agent/mcp path private. Port 3001
is published by Compose, so restrict it at the host or proxy when direct access
is not intended. See the MCP guide for the protocol boundary.
Do not expose PostgreSQL, Supavisor, or the generated .env to the public
network.
The default setup is intended for a single host. Back up the stack directory and Docker volumes according to your operational requirements.
Agent exposure, retention, readiness, and rollback requirements are documented in the Agent guide.
The Agent profile includes authenticated private Redis with a persistent AOF volume. Back up that volume with the stack, but treat PostgreSQL checkpoints and history as authoritative after cache loss. New execution requires Redis; history reads do not. Readiness checks storage, delivery/cache, and MCP, not one user's provider entitlement. Preserve encryption keys through upgrades, drain legacy runs before changing execution mode, and use a history-compatible image during rollback. Personal keys and platform identity stay server-only.
Images retain canonical latest tags and also carry immutable sha-<commit>
tags. Record a matching image set or digests for restore instead of assuming
latest still names the prior release.
Keep the application, migration runner, MCP, and optional Agent images on the
same release channel. Back up PostgreSQL and the private stack configuration,
pull the images, rerun init so tracked configuration and Edge Functions are
refreshed without replacing existing .env values, then converge Compose:
STACK_DIR="/absolute/path/to/monei-stack"
cd "$STACK_DIR"
test -f .env
test -f compose.yaml
docker pull moneiai/app:latest
docker pull moneiai/migrations:latest
docker pull moneiai/mcp:latest
# Required when the Agent profile is enabled.
docker pull moneiai/agent:latest
docker run --rm \
-v "$STACK_DIR:/data" \
-e MONEI_DATA_DIR=/data \
moneiai/app:latest init
docker compose --env-file .env pull
docker compose --env-file .env up -d --wait
When optional profiles are enabled, include them in the final Compose command.
Confirm the migration job completes before the dependent services start, then
check application health and the affected domain flows. Review upstream Supabase
release changes before changing SUPABASE_VERSION; its distribution is
downloaded separately and can be upgraded independently.
The migration runner commits each SQL migration and its history entry in one
transaction under a session advisory lock. PostgreSQL-confirmed deadlocks and
serialization aborts receive at most three attempts with bounded backoff.
The administration domain is delivered as one initial migration with its
historical billing backfill; the Go runner reads the complete file atomically.
Permission, syntax, connection, and unknown commit failures are not retried.
If the job fails, resolve the reported cause and rerun
docker compose --env-file .env up -d --wait; committed history allows the
job to continue without resetting the database. Preserve the private .env
and credential keyring with the database backup.
The self-hosted stack supports the same classification worker and provider boundaries as the hosted application. Configuration, queue behavior, provider identity, local validation, production operation, and failure handling are documented in the classification guide.
The stack exposes the same-origin analytics proxy and propagates its server-side settings without adding analytics secrets to the frontend. See the analytics guide for opt-out behavior, configuration, local validation, production routing, and privacy constraints.
Investment positions, proceeds, collection, valuation coverage, and balance semantics are documented in the portfolio guide and the Open Finance guide. The Docker distribution uses the same migrations and Edge Functions; update the application and migration images together when those contracts change.
Accounts with a verified authenticator require AAL2 to access application data, including direct REST/RPC calls outside the browser. Password-only AAL1 tokens remain available for the Auth challenge but are rejected by the application backend. Update the migration, application, MCP and Agent images together; the migration configures the existing PostgREST pre-request hook and restrictive RLS policies without new runtime settings. See authentication for session restoration, enforcement and local verification. Preserve database configuration loading and the MFA pre-request hook when customizing PostgREST.
The stack enables authenticator MFA enrollment and verification. Platform Owner is provisioned once for an existing account through a private database-owner connection, independently of workspace ownership. Bootstrap and recovery are audited and never run automatically during initialization. See platform administration for the SQL operation and access boundary.
Billing maintenance also establishes enforcement readiness while enforcement is disabled. The existing scheduler, Stripe configuration, API URL and SMTP outbox are reused; no additional command or runtime secret is required. Before first enforcement activation or signing-secret rotation, send a signed test event from Stripe to the configured callback and allow maintenance to validate
Content type
Image
Digest
sha256:348688292…
Size
11 MB
Last updated
about 11 hours ago
docker pull moneiai/app