Self-host Meratyn
Meratyn is a portable Node service for exchanging .meratyn.json
projects. The monorepo includes an authenticated admin workspace, version
admin, collections, personal API tokens, thumbnails, and the HTTP contract
used by the Meratyn desktop Share dialog.
It no longer depends on Cloudflare:
- Bun serves the TanStack Start SPA shell and API from one process.
- SQLite stores users, metadata, collections, and SHA-256 API-token hashes.
- A mounted filesystem stores immutable project versions and thumbnails.
- Clerk remains optional for the admin console only. Viewer, desktop, and embed surfaces stay local-first and do not inherit admin authentication; Meratyn does not add a second hosted identity provider for those surfaces.
- A lightweight nginx container serves the local Meratyn viewer used by embedded maps and “Open in Meratyn” links.
Run with Docker Compose
Docker Desktop or another Docker Engine with Compose v2 is required.
cp .env.example .env
docker compose -f docker/compose/admin.yaml up --build -d
docker compose -f docker/compose/admin.yaml ps
Open https://admin.mappview.mapptech.com.co. The Meratyn viewer runs at
https://mappview.mapptech.com.co. The default Compose profile
creates a local account named local-mapper for API and test access using the
development-only ADMIN_LOCAL_TOKEN from the copied .env file. The
browser remains signed out by default and shows only the authentication gate.
To open the offline workspace without Clerk, set ADMIN_BROWSER_TOKEN to
the same value as ADMIN_LOCAL_TOKEN and rebuild. If Clerk keys are
present, the Clerk session takes precedence over that fallback and the
workspace uses Clerk’s modal sign-in and user menu.
Do not reuse the example token outside loopback development. Replace it with a
long random mappview_… value before exposing the service to another machine.
To stop the service without deleting data:
docker compose -f docker/compose/admin.yaml down
Project data persists through the logical volume management-data. Its default
Docker volume name is mappview-management-data, and the database is stored at
/data/admin.db. Set ADMIN_DATA_VOLUME or ADMIN_DB_PATH when
intentionally choosing a different location.
To explicitly delete local test data, add --volumes to the down command.
To publish and manage local GIS files, start the optional headless GeoNode and GeoServer profile:
docker compose -f docker/compose/admin.yaml --profile hosted-layers up --build -d
Replace the development passwords in .env.example first. See
Deploy hosted layers for the service topology,
configuration, and production requirements.
Load the sample vector and raster layers
After the hosted-layers profile is healthy, seed two small public examples:
bun run seed:online-layers
The command reads ADMIN_LOCAL_TOKEN from the shell or repository-root .env,
uploads tests/fixtures/sample-colombia-cities.geojson and
tests/fixtures/striped.tif, waits for publication, and prints their Share
admin URLs. It is idempotent for samples it previously created. It also
fails if publication changes the user-entered title or returns an internal
publication detail in the public layer model.
Configuration
Set Compose substitutions in a local .env file or in the shell:
| Variable | Default | Purpose |
|---|---|---|
ADMIN_BIND_ADDRESS | 127.0.0.1 | Host interface used to publish Share |
ADMIN_PORT | 8787 | Published host port |
ADMIN_TRUST_PROXY | false | Trust CF-Connecting-IP or the first X-Forwarded-For address for view deduplication |
VIEWER_BIND_ADDRESS | 127.0.0.1 | Host interface used to publish the viewer |
VIEWER_PORT | 8080 | Published local Meratyn viewer port |
APP_ORIGIN | https://admin.mappview.mapptech.com.co | Canonical admin origin |
ADMIN_LOCAL_TOKEN | local development value | Bootstrapped mappview_… token |
ADMIN_BROWSER_TOKEN | empty | Optional local token compiled into the browser for offline dashboard testing |
ADMIN_BOOTSTRAP_USERNAME | local-mapper | Local account username |
MAX_PROJECT_BYTES | 26214400 | Maximum project size |
DEFAULT_STORAGE_QUOTA_BYTES | 104857600 | Per-user storage quota |
FIELD_PHOTO_MAX_BYTES | 26214400 | Maximum size of each field photo authorized for direct storage upload |
VIEWER_URL | https://mappview.mapptech.com.co | Viewer used for project links |
CLERK_PUBLISHABLE_KEY | empty | Clerk key compiled into the TanStack Start frontend |
CLERK_SECRET_KEY | empty | Clerk backend secret used to verify session tokens |
CLERK_JWT_KEY | empty | Optional PEM public key alternative to the Clerk secret |
CLERK_AUTHORIZED_PARTIES | empty | Comma-separated allowed browser origins |
GEONODE_CATALOGUE_SYNC_INTERVAL_MS | 300000 | Interval for importing and reconciling GeoNode catalogue records; minimum 60 seconds |
STUDIO_PASSWORD | empty | Password for the demo studio at /studio; empty turns the studio off |
MAPPVIEW_NO_EXTERNAL_CDN | empty | Build-time viewer policy (1 bundles controlled assets and fails on emitted CDN URLs) |
CORS_ORIGINS includes the local viewer and Tauri WebView origins by default so
the viewer and a desktop build configured for this self-host can fetch project
JSON from Share. If either published port changes, update VIEWER_URL,
APP_ORIGIN, and CORS_ORIGINS together before rebuilding.
STUDIO_PASSWORD enables the demo studio at /studio: one password, no
accounts, and a 12-hour session cookie. The studio sets the scenario values
behind the live showcases (Mocoa rainfall flooding, Bogotá air quality, and the
Medellín smart city). Their layers are public at
/api/studio/feeds/<feed>/layers/<layer>, combine the saved values with live
Open-Meteo data, and are rendered once per 5-second tick, so a save reaches
an open showcase within seconds. Each layer carries an ETag, so viewers skip
layers that did not change since the previous tick. The showcase projects
load these layers from STUDIO_FEED_ORIGIN in
scripts/assets/showcases/studio.ts; change it and rebuild the showcases to
serve them from another deployment.
The browser applications also compile links to the surrounding Meratyn
services. Local values are listed in .env.defaults; override these when the
services live on different domains. The localhost defaults only resolve on
the machine running Compose — before exposing the deployment, set each
VITE_MAPPVIEW_*_URL to its public HTTPS origin; otherwise generated project
links, embeds, and help links will point at the user’s own machine:
| Variable | Local default | Used for |
|---|---|---|
VITE_MAPPVIEW_WEBSITE_URL | https://mappview.mapptech.com.co | Main-site and help links |
VITE_MAPPVIEW_VIEWER_URL | https://mappview.mapptech.com.co/ | Viewer, embeds, and HTML exports |
VITE_MAPPVIEW_SHARE_URL | https://admin.mappview.mapptech.com.co | Project and hosted-layer APIs |
VITE_MAPPVIEW_TILES_URL | https://tiles.mappview.mapptech.com.co | Tile, OAM, Source Cooperative, and planetary proxies |
VITE_MAPPVIEW_PLUGINS_URL | viewer origin | Plugin marketplace origin |
VITE_MAPPVIEW_PLUGIN_REGISTRY_URL | https://plugins.mappview.mapptech.com.co/registry.json | Curated plugin registry (served by the plugins container) |
VITE_MAPPVIEW_TRUSTED_PLUGIN_REGISTRY_URL | https://plugins.mappview.mapptech.com.co/registry.json | Deployment-approved external plugins |
VITE_MAPPVIEW_DATASETS_URL | http://localhost:8790 | GeoLens sample catalogue |
The desktop Vite build reads these from the repository-level .env as well as
the app-local environment. They are build-time values, so rebuild browser
images after changing them. The Python widget uses
MAPPVIEW_VIEWER_URL (and accepts VITE_MAPPVIEW_VIEWER_URL as a fallback).
For a Docker runtime, MAPPVIEW_CAPABILITIES is an optional comma-separated
authority policy shared by the viewer and sidecar. Use it with a kiosk or
classroom UI profile when those actions must be denied, rather than relying on
the visible menu alone. The sidecar requires both processing:run and
processing:sidecar; see deployment capabilities.
The trusted registry document itself is fetched at runtime. After its URL has been compiled into the viewer, publishing new versioned entries to that same URL does not require another viewer deployment.
The default ports bind only to loopback because the default bootstrap token is a
known local-development credential. Before setting either bind address to
0.0.0.0 or another externally reachable interface, replace
ADMIN_LOCAL_TOKEN with a long random value or configure Clerk. Only set
ADMIN_BROWSER_TOKEN on a trusted local machine: it embeds that credential in
the browser bundle and therefore must never be used for an internet-facing
deployment.
Clerk authentication
Clerk’s Hobby plan is sufficient for local testing and small deployments. It
does not require a credit card and includes the prebuilt sign-in, sign-up, and
user-profile interfaces. Copy .env.example to .env, create an application
in the Clerk dashboard, and fill in:
CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_AUTHORIZED_PARTIES=http://localhost:8787,http://127.0.0.1:8787,http://localhost:5174,http://127.0.0.1:5174
Then rebuild because TanStack Start and Vite compile the publishable key into the browser bundle:
docker compose -f docker/compose/admin.yaml up --build -d
The frontend uses @clerk/tanstack-react-start with ClerkProvider, a modal
SignInButton, UserButton, and session getToken() for authenticated API
requests. The Node service verifies those tokens with @clerk/backend. No
Clerk key is committed to the repository.
The two 5174 origins authorize the split development server started by
bun run dev:admin; the launcher also merges them into
CLERK_AUTHORIZED_PARTIES automatically. Keep the 8787 origins for the
same-origin Compose build. Add every other browser origin that can issue Clerk
tokens, including both hostname variants when your team uses both, or the API
will correctly reject those sessions with HTTP 401.
Production authentication
For a non-local deployment, explicitly set ADMIN_LOCAL_TOKEN and
ADMIN_BROWSER_TOKEN to empty values, provide CLERK_PUBLISHABLE_KEY, and set CLERK_SECRET_KEY or
CLERK_JWT_KEY. Set CLERK_AUTHORIZED_PARTIES, APP_ORIGIN, and
CORS_ORIGINS to the actual browser, viewer, and Tauri WebView origins. Set
ADMIN_BIND_ADDRESS for the reverse proxy’s network path and put TLS in front
of port 8787. Set ADMIN_TRUST_PROXY=true only when Share is reachable
exclusively through a proxy that replaces CF-Connecting-IP or
X-Forwarded-For; this preserves per-client view counts without allowing
direct clients to spoof their address.
The Compose file is deliberately optimized for local testing. Production
operators should use secrets rather than plain environment values and should
back up the /data volume.
Develop without Docker
Use Bun 1.4.2 or later:
bun install
bun run dev:admin
The command starts the admin API on port 8787 and the Vite frontend on
https://admin.mappview.mapptech.com.co, with source watching for both processes. It loads the
repository-root .env and maps CLERK_PUBLISHABLE_KEY to Vite; when Clerk is
not configured, it supplies a matching local-development token instead. The
launcher reuses a healthy admin API already listening on port 8787, such as the
Docker Compose service. The service stores local data in
services/admin/data/admin.db and services/admin/data/objects; the directory
is ignored by Git. Set ADMIN_BOOTSTRAP_TOKEN or VITE_ADMIN_LOCAL_TOKEN before
starting to override the default development token.
Run the portable integration suite with:
bun run test:admin
It verifies the built TanStack Start shell, health endpoint, publishing, raw downloads, versions, forks, thumbnails, collections, private access, and hashed personal tokens.
With the Compose stack running, verify route parity, the local viewer request, required mobile widths, and accessibility with:
bun run test:admin:e2e
Connect Meratyn desktop
Set the desktop/web build variable to your admin origin:
VITE_MAPPVIEW_SHARE_URL=http://localhost:8787
Packaged desktop builds use browser fetch for custom Share origins. The Share
service must therefore include tauri://localhost and
http://tauri.localhost in CORS_ORIGINS; the Compose defaults already do so.
Users create a personal token under Meratyn Settings, then paste that mappview_…
token into Meratyn Settings. Project source versions are immutable; restoring
a version creates a new one.
Backup and restore
Back up the entire /data volume as one unit. admin.db contains metadata,
while objects/ contains project JSON and thumbnails. Restoring only one side
will leave the project inventory and stored files inconsistent.
Engram cloud memory
Engram runs as a standalone stack in docker/compose/engram.yaml — it is NOT part
of docker/compose/admin.yaml. The engram container serves cloud serve with
the container listening on internal port 18080, published loopback-only on
host port 127.0.0.1:18083 -> 18080.
It is backed by the EXISTING VPS postgres — the geonode postgis container
on the openship-mappview-admin network, engram database owned by the
engram role — so there is no dedicated engram-db service and no
engram-db-data volume here.
Public route: https://engram.mappview.mapptech.com.co via the host nginx
TLS proxy in scripts/deploy/nginx/engram.mappview.mapptech.com.co.conf.
The container stays loopback-only; nginx proxies all paths to
http://127.0.0.1:18083 with standard forwarded headers. TLS requires a
Let’s Encrypt certificate for this hostname
(/etc/letsencrypt/live/engram.mappview.mapptech.com.co/); port 80 serves
the ACME challenge and redirects to HTTPS.
Engram runs in authenticated mode. Dashboard login is /dashboard/login
(full URL https://engram.mappview.mapptech.com.co/dashboard/login).
Managed admin tokens can log into the dashboard and sync. Raw tokens are
never committed or printed in docs: the local CLI reads
ENGRAM_CLOUD_TOKEN at runtime from the environment or a secret manager.
Start it alongside the admin stack with an explicit file selection:
docker compose -f docker/compose/engram.yaml up --build -d
Configure it with server-side environment only — copy the ENGRAM_* names
from .env.example into the server environment and set real values there.
ENGRAM_DATABASE_URL points at the existing VPS postgres
(postgres://engram@geonode-db:5432/engram, password in the URL, secret).
The project allowlist is fixed to meratyn in Compose.
Operator outline (VPS-side, after this commit lands):
- Install the nginx config and issue the certificate for
engram.mappview.mapptech.com.co, then reload nginx. - Bootstrap one managed admin token server-side and distribute managed tokens to developers. Never commit token values.
- Developers follow “Shared Engram memory” in the root
README.md(server config, enroll, sync, status).
Enroll a machine with:
engram cloud enroll mappview
Back up the memory rows together with geonode: they live in the engram
database inside the existing VPS postgres, so they are covered by the
geonode database backup, not by a separate volume. Never bring
the stack down with volume deletion. For cloud deploys, see
odd/tasks/engram-cloud-service.md (standalone -ServiceNames selection
after one --full reconcile).