Mortar deployment
Mortar is a stateless Go binary that connects to four managed services for state. It runs identically on a developer’s laptop, a single VPS, or a horizontally-scaled production cluster — what changes is which backends you point it at.
This doc covers: required runtime, the four backends and their swappable implementations, a self-host quickstart, and the hosted-vs-self-host architecture.
Required runtime
- Go 1.25+ (build only; the released binary is static)
- Postgres 16+ with
pgcryptoextension installable - Redis-compatible cache (any version that speaks RESP)
- HTTP-callable function runner (or local subprocess for dev)
- S3-compatible object storage (or local disk for dev)
The Mortar binary runs on any Linux x86_64 / ARM64 with zero system dependencies (musl-static). Memory: ~64 MB RSS at idle. CPU: scales with request rate.
Backend compatibility matrix
Mortar’s five primitives (database, cache, storage, compute, network)
each have a swappable interface
(internal/primitive/{database,cache,storage,compute,network}).
Implementations live under internal/backend/<vendor>/.
Database (primitive/database)
| Backend | Hosted | Self-host | Notes |
|---|---|---|---|
| Self-hosted PostgreSQL 16+ | — | ✓ | Any VM, Docker, K8s. Requires pgcrypto. |
| AWS RDS PostgreSQL 16+ | — | ✓ | All Mortar features supported (RLS, pg_notify). |
| AWS Aurora PostgreSQL 16+ | — | ✓ | Compatible mode; faster scale-out. |
| Aliyun RDS PostgreSQL HA | ✓ | ✓ | Mortar Cloud’s choice; available in mainland China + overseas. |
| Aliyun PolarDB PostgreSQL | — | ✓ | Aurora-equivalent on Aliyun. |
| Tencent Cloud TDSQL-C / CDB PostgreSQL | — | ✓ | |
| Google Cloud SQL PostgreSQL | — | ✓ | |
| Neon / Supabase Postgres | — | ✓ | Yes, you can run Mortar on top of Supabase if you want. |
| CockroachDB / YugabyteDB | — | ✗ | Not Postgres-protocol-complete enough for our RLS. |
Required Postgres features:
pgcryptoextension (forgen_random_uuid())- Row-Level Security (Postgres-native, all versions)
pg_notify/ LISTEN (Postgres-native)- JSONB + GIN indexes (Postgres-native)
Mortar does not require any non-allowed RDS extension. See the Optional extensions section for what you can opt into without breaking Mortar.
Storage (primitive/storage)
| Backend | Hosted | Self-host | Notes |
|---|---|---|---|
| LocalDisk | — | ✓ | Dev / single-VPS only. No signed URLs. |
| Aliyun OSS | ✓ | ✓ | Mortar Cloud’s choice. |
| Tencent Cloud COS | — | ✓ | (planned) |
| AWS S3 | — | ✓ | (planned) |
| MinIO / any S3-compatible | — | ✓ | Self-host; OSS-compatible signature. |
Cache (primitive/cache)
| Backend | Hosted | Self-host | Notes |
|---|---|---|---|
| InMemoryCache | — | ✓ (dev) | Single-process; no pub/sub across nodes. |
| Aliyun Tair (in-memory) | ✓ | ✓ | Mortar Cloud’s choice. Redis-compatible. |
| Tencent Cloud Redis | — | ✓ | (planned) |
| AWS ElastiCache | — | ✓ | (planned) |
| Self-hosted Redis / KeyDB / Dragonfly | — | ✓ | Any RESP-speaking server. |
Compute (primitive/compute)
| Backend | Hosted | Self-host | Notes |
|---|---|---|---|
| LocalSubprocess | — | ✓ (dev) | Spawns node / deno in a subprocess. |
| Aliyun Function Compute FC | ✓ | ✓ | Mortar Cloud’s choice. Per-CU billing. |
| Tencent Cloud SCF | — | ✓ | (planned) |
| AWS Lambda | — | ✓ | (planned) |
| Cloudflare Workers | — | — | Roadmap; cold-start latency advantage. |
| Self-hosted Deno runtime | — | ✓ | Any Deno-compatible HTTP-invokable runner. |
Self-host quickstart
# Pull the binary
curl -L https://github.com/liamxujia/appunvs/releases/latest/download/mortar-linux-amd64 -o mortar
chmod +x mortar
# Set the four backends + auth secret
export MORTAR_DB_URL="postgres://user:pass@host:5432/mortar?sslmode=require"
export MORTAR_REDIS_URL="redis://host:6379/0"
export MORTAR_STORAGE_BACKEND="local" # or "oss" / "cos" / "s3" / "minio"
export MORTAR_STORAGE_LOCAL_ROOT="/var/mortar/files"
export MORTAR_JWT_SECRET="$(openssl rand -hex 32)"
# Apply schema migrations (explicit — the server never migrates at boot)
MORTAR_DB_URL="$MORTAR_DB_URL" ./migrate up # or: make migrate-up
# Boot — verifies the schema is at the expected version, then serves
./mortar
# → http://localhost:8080/health
Schema is owned by versioned goose migrations (under
internal/primitive/database/migrations/) and applied via the explicit
migrate up step. The server does NOT change schema at boot — it only
verifies the DB is migrated and fails fast otherwise, so a deploy can never
silently alter the schema. GORM remains the ORM for all CRUD.
That’s it. The binary will:
- Connect to Postgres, ping it
- Verify the goose schema version matches what the binary expects
(fatal with “run
make migrate-up” if the DB is behind) - Verify the DB role can’t bypass RLS (NOSUPERUSER / NOBYPASSRLS)
- Connect to Redis (best-effort ping)
- Mount the HTTP API on
MORTAR_LISTEN(default:8080)
The migration itself (run once per deploy, before booting the new binary) creates pgcrypto, the tables, RLS policies, and the pg_notify trigger.
Recommended for production self-host
- Postgres: managed RDS or equivalent. Multi-AZ for HA.
- Reverse proxy: nginx / Caddy / ALB in front for TLS.
- Mortar binary: 2+ replicas behind a load balancer (the binary is stateless — replicas are interchangeable).
- Storage: object storage if you’ll have any non-trivial file uploads. LocalDisk is fine for proof-of-concept.
- Backup: trust your Postgres provider’s PITR. Mortar holds no state outside Postgres + the storage backend.
Skip for production self-host
MORTAR_NOISY_NEIGHBOR_MONITOR is off by default for self-host
(set =on only on multi-tenant deployments). Single-tenant
self-hosters don’t have noisy neighbors by definition.
Hosted vs self-host architecture
Same binary, different wiring:
SELF-HOST (single tenant, simplest case):
┌─────────────────────────┐
│ Mortar binary │ ─── connect ───▶ User's Postgres
│ - HTTP API │ (RDS / Aurora /
│ - Realtime listener │ self-hosted PG)
│ - Plan gate │
│ - Usage recorder │ ─── connect ───▶ User's Redis
│ (single process, often │ (ElastiCache /
│ single-tenant) │ self-hosted)
└─────────────────────────┘ ─── connect ───▶ User's S3 / MinIO
─── invoke ───▶ User's Lambda /
Deno / FC
MORTAR CLOUD (multi-tenant hosted production):
┌─────────────────────────┐
│ Mortar binary x N │ ─── connect ───▶ Aliyun RDS PG
│ (behind ALB) │ (shared pool +
│ + Noisy-neighbor mon │ RLS multi-tenant)
│ + Plan gate / Recorder │
└─────────────────────────┘ ─── connect ───▶ Aliyun Tair
─── connect ───▶ Aliyun OSS
─── invoke ───▶ Aliyun FC
The Mortar binary is bit-for-bit identical in both deployments. The difference is environment variables + the noisy-neighbor monitor toggle.
Optional extensions
Mortar doesn’t require them, but they don’t conflict either — turn them on if your app needs them.
PostGIS (geographic / spatial)
Supported on every major managed Postgres:
| Provider | Version | Enable |
|---|---|---|
| AWS RDS | 3.4.x | CREATE EXTENSION postgis; |
| Aliyun RDS | 3.x | Same; bonus: Aliyun Ganos (PostGIS-compatible) |
| Tencent Cloud CDB | 3.x | Same |
| Aurora PG | 3.4.x | Same |
| Google Cloud SQL | 3.x | Same |
| Self-hosted PG | any | apt install postgresql-16-postgis-3 etc. |
Mortar’s app_rows.data JSONB doesn’t conflict with PostGIS columns.
You can either store coords as JSONB paths (data->'$.lat') or add
explicit geometry columns via raw migration and use PostGIS native
operators on them.
TimescaleDB / pg_partman
Self-host friendly. Some RDS variants restrict; check your provider. Mortar doesn’t depend on either.
Custom user-installed extensions
Mortar’s goose migrations touch only its own tables (projects,
api_keys, app_users, app_tables, app_columns, app_rows,
app_files, …). Anything else in your database — your tables, your
extensions, your views — Mortar ignores.
Migration from Mortar Cloud → self-host
Coming soon. Roadmap:
mortar export --project=<id>— dumps all per-project tables + storage objects + metadata to a tarballmortar import --target-db=<dsn>on the new self-hosted instance — loads the tarball, runs Setup, tenant ID preserved
This is a key open-source guarantee: never lock you in. You can always leave Mortar Cloud and run the same binary against your own infrastructure.