This document lists all environment variables supported by Activity.next.
Only getting started? The Quickstart covers the three core settings strictly required to boot a local instance (host, secret phrase, and a database).
Application configuration is provided through environment variables. Root-level config.json files are ignored; migrate any previous file settings to the corresponding ACTIVITIES_* or OTEL_EXPORTER_* variables listed below. Application config is read at runtime, so Docker/standalone builds do not need real ACTIVITIES_* or OTEL_EXPORTER_* values at build time. Environment variables read outside app config, such as NODE_ENV, BUILD_STANDALONE, NEXT_TELEMETRY_DISABLED, and LOG_LEVEL, still apply.
Some instance policy is also editable at runtime from the admin area
(Admin → Instance / Posts & media / Network, and the Federation tab),
stored in the server_settings table. Each such value is resolved with the
precedence environment variable → database → built-in default: an
environment variable always wins and locks the field in the admin UI with a
"Set by environment" badge, so removing the variable is what hands control back
to the admin form. Clients read the resolved values from /api/v1/instance and
/api/v2/instance.
Every endpoint that creates or edits a status enforces the resolved
posts.maxCharacters and polls.* limits — POST/PUT /api/v1/statuses[/:id] and POST /api/v1/accounts/outbox, which is the
endpoint the web composer and the inline reply box post through. Every upload
endpoint (POST /api/v1/media, POST /api/v2/media, PUT/PATCH /api/v1/media/:id thumbnails, POST /api/v1/medias/presigned, PATCH /api/v1/accounts/update_credentials avatars/headers, and admin custom emoji)
enforces the resolved media.maxFileSize. All of them answer 422 above the
limit. The status routes put the offending limit in the error message (except
the poll-expiry one, which just says it is out of range); the upload routes
return the generic Unprocessable entity, apart from
update_credentials, which says Invalid image file. The web composer, its poll
editor and media picker, the inline reply box, and the avatar/header picker all
size themselves to the same resolved values rather than to fixed constants, so
in normal use the client does not offer what the endpoint will refuse.
network.linkPreviews is edited on Admin → Network under Link previews
and also has no environment variable, so it is never locked. It is not a
features.* switch because it does not change navigation: it controls whether
this server makes outbound requests to the pages people link to. Turning it off
stops new fetches immediately; cards already stored keep rendering, and the
Mastodon card field simply stays null for statuses posted afterwards.
The features.* settings (features.fitness, features.explore,
features.messages) are edited on Admin → Instance under Optional
features and have no environment variable, so they are never locked. Turning
one off removes that section from every account's navigation — sidebar, rail
and mobile drawer. Settings → Navigation still lists it, greyed out and marked
"off for this server", because that page is where someone goes to find out
where a nav item went. It does not disable the
section: its pages and API keep working, so an existing link or bookmark still
resolves, and nobody's saved navigation is deleted, so re-enabling a feature
restores each account's layout exactly as they left it.
posts.maxMediaAttachments is the exception to the status-limit enforcement
described at the top of this section: it is advertised to clients and honoured
by this instance's own composer and inline reply box (which read it through
useInstanceLimits()), but no route enforces the resolved value. All three
create/edit paths — POST/PUT /api/v1/statuses[/:id] and POST /api/v1/accounts/outbox — instead reject an attachment list longer than the
fixed MAX_STORED_MEDIA_ATTACHMENTS ceiling (50), answering 422. So lowering
posts.maxMediaAttachments changes what clients are told and what the built-in
composer offers, never what these routes accept; an API client that ignores the
advertised value can still store up to the ceiling. Note that this is specific
to the attachment count: the outbox route does enforce the resolved
posts.maxCharacters and polls.* through validateStatusContentLimits,
which is why the general claim at the top of this section holds for every limit
but this one.
Several settings carry an upper bound. polls.maxOptions (50) and
polls.maxCharactersPerOption (1,000) match the ceilings the status create
schema accepts; posts.maxMediaAttachments matches the stored-media ceiling;
posts.maxCharacters (100,000) is a sanity bound only. media.maxFileSize
defaults to 200 MiB (209715200) and can be lowered freely or raised to a
ceiling of 1 GiB (1073741824). The ceiling is there because the
object-storage driver buffers a stored file in memory when it reads it back out;
that read bounds itself by the same resolved media.maxFileSize, so raising the
cap never stores media the instance would then refuse to serve. An
ACTIVITIES_MEDIA_STORAGE_MAX_FILE_SIZE above the ceiling still applies,
because an environment variable pins its setting outright.
The variables that pin an admin-editable setting are:
ACTIVITIES_SERVICE_NAME, ACTIVITIES_SERVICE_DESCRIPTION,
ACTIVITIES_LANGUAGES, ACTIVITIES_REGISTRATION_OPEN,
ACTIVITIES_ALLOW_EMAILS, ACTIVITIES_MEDIA_STORAGE_MAX_FILE_SIZE,
ACTIVITIES_REQUEST_TIMEOUT, ACTIVITIES_REQUEST_RETRY,
ACTIVITIES_REQUEST_MAX_RESPONSE_SIZE_BYTES, ACTIVITIES_FEDERATION_MODE, and
ACTIVITIES_ALLOW_ACTOR_DOMAINS. Post size and poll limits have no environment
variable and are database-or-default only. ACTIVITIES_ALLOW_MEDIA_DOMAINS and
the ACTIVITIES_MEDIA_STORAGE_* backend stay environment-only (they feed the
Edge-runtime CSP and storage infrastructure). ACTIVITIES_ALLOW_MEDIA_DOMAINS
is shown read-only on the Federation tab, and the resolved storage backend is
shown read-only on the Posts & media tab. On a multi-instance deployment, an admin's save takes effect
on other instances within a short cache window rather than instantly; an
environment variable, when set, always wins regardless.
The Posts & media tab also carries a Configure environment builder for the
infrastructure that stays environment-only: it assembles a copy-pasteable .env
block for media storage (ACTIVITIES_MEDIA_STORAGE_*) and for the fitness map
provider (ACTIVITIES_FITNESS_MAP_PROVIDER and its credentials). The builder is
inert — nothing typed into it is submitted, stored, or sent anywhere, and the
server still only reads those values from the environment at boot, so a change
needs a restart.
A required variable you have not filled in is listed in the block carrying a
value only when the example is itself a correct one to keep — today that is just
ACTIVITIES_MEDIA_STORAGE_PATH=./uploads — and empty otherwise (NAME=), never
its example. The example belongs to the input beside it: a block that carried
examples as values could be pasted verbatim and boot a real configuration out of
them, pointing a live instance's uploads at a bucket the operator does not own.
Note that ./uploads is a default of the builder's field, not of the variable:
none of the storage variables has an app-level default, so omitting a required
one from your .env fails config validation rather than falling back. An
optional variable is left out of the block entirely until you type something
into it, which is why ACTIVITIES_MEDIA_STORAGE_ENDPOINT does not appear by
default.
An emitted NAME= is inert because a blank required value is rejected rather
than accepted — see Media Storage below for that rule.
| Variable | Required | Description |
|---|---|---|
ACTIVITIES_HOST |
Yes | Domain name for your instance (e.g., social.example.com). No protocol, no trailing slash. |
ACTIVITIES_SECRET_PHASE |
Yes | Secret string for signing cookies and tokens. Generate with openssl rand -base64 32. |
ACTIVITIES_SERVICE_NAME |
No | Public instance display name used by instance metadata, auth display, and WebAuthn issuer labels. |
ACTIVITIES_SERVICE_DESCRIPTION |
No | Public instance description used by instance metadata endpoints. |
ACTIVITIES_PRIVACY_POLICY |
No | Plain-text privacy policy served (HTML-escaped, paragraph-wrapped) by GET /api/v1/instance/privacy_policy. When unset the endpoint returns 404. (Mastodon serves a bundled default policy instead; this server has none, so unset means 404.) |
ACTIVITIES_TERMS_OF_SERVICE |
No | Plain-text terms of service served (HTML-escaped, paragraph-wrapped) by GET /api/v1/instance/terms_of_service (effective date reported as 1970-01-01). When unset the endpoint returns 404, matching Mastodon. |
ACTIVITIES_LANGUAGES |
No | JSON array of supported instance languages (e.g., ["en","nl"]). Defaults to ["en"]. |
ACTIVITIES_ALLOW_EMAILS |
No | JSON array of email addresses allowed to register (e.g., ["user@example.com"]). If unset, registration may be open. |
ACTIVITIES_REGISTRATION_OPEN |
No | Set to false to close new-account sign-up entirely (sign-in stays available; the logged-out landing shows a "registration closed" notice). Defaults to open. Orthogonal to ACTIVITIES_ALLOW_EMAILS, which restricts who may register while open. |
ACTIVITIES_TRUSTED_HOSTS |
No | JSON array of additional public hosts accepted from X-Forwarded-Host and X-Activity-Next-Host. |
ACTIVITIES_INSECURE_AUTH |
No | Set to true to allow HTTP (non-HTTPS) authentication. Only for local development. |
| Variable | Description |
|---|---|
ACTIVITIES_TRUST_PROXY_IP_HEADERS |
Set to true to use proxy-managed client IP headers for unauthenticated app registration throttling. Only enable when all direct app access is blocked and the trusted proxy overwrites or strips client-supplied forwarding headers before setting its own. Do not enable behind append-only proxies; if the proxy appends to X-Forwarded-For, the first element may still be untrusted. |
Activity.next supports SQLite and PostgreSQL. The configuration loader also accepts MySQL-compatible Knex clients for deployments that provide the needed driver/runtime support. See SQLite Setup and PostgreSQL Setup for detailed guides.
| Variable | Description |
|---|---|
ACTIVITIES_DATABASE |
Full database configuration as a JSON string (e.g., {"client":"pg","connection":{...}}). The value is a Knex configuration object passed straight to knex(). Note: only the app runtime reads this variable — yarn migrate (the Knex CLI) does not; use the individual ACTIVITIES_DATABASE_* variables below when running migrations. |
| Variable | Description |
|---|---|
ACTIVITIES_DATABASE_CLIENT |
Set to better-sqlite3 or sqlite3 for SQLite. |
ACTIVITIES_DATABASE_SQLITE_FILENAME |
Path to SQLite database file (e.g., ./dev.sqlite3). |
| Variable | Description |
|---|---|
ACTIVITIES_DATABASE_CLIENT |
Set to pg or pg-native for PostgreSQL. |
ACTIVITIES_DATABASE_PG_HOST |
PostgreSQL host. |
ACTIVITIES_DATABASE_PG_PORT |
PostgreSQL port (default: 5432). |
ACTIVITIES_DATABASE_PG_USER |
PostgreSQL username. |
ACTIVITIES_DATABASE_PG_PASSWORD |
PostgreSQL password. |
ACTIVITIES_DATABASE_PG_DATABASE |
PostgreSQL database name. |
ACTIVITIES_DATABASE_PG_SSL_MODE |
PostgreSQL SSL mode: disable, require, verify-ca, or verify-full. When set to require, SSL is enabled without certificate verification. When set to verify-ca, SSL is enabled with certificate verification but without hostname checking. When set to verify-full, SSL is enabled with full certificate and hostname verification. When set to disable or unset, SSL is not used. |
ACTIVITIES_DATABASE_PG_POOL_MIN |
Minimum connection pool size. |
ACTIVITIES_DATABASE_PG_POOL_MAX |
Maximum connection pool size. |
| Variable | Description |
|---|---|
ACTIVITIES_DATABASE_CLIENT |
Set to mysql or mysql2 for MySQL. |
ACTIVITIES_DATABASE_MYSQL_HOST |
MySQL host. |
ACTIVITIES_DATABASE_MYSQL_PORT |
MySQL port (default: 3306). |
ACTIVITIES_DATABASE_MYSQL_USER |
MySQL username. |
ACTIVITIES_DATABASE_MYSQL_PASSWORD |
MySQL password. |
ACTIVITIES_DATABASE_MYSQL_DATABASE |
MySQL database name. |
ACTIVITIES_DATABASE_MYSQL_POOL_MIN |
Minimum connection pool size. |
ACTIVITIES_DATABASE_MYSQL_POOL_MAX |
Maximum connection pool size. |
| Variable | Description |
|---|---|
ACTIVITIES_AUTH |
Full auth configuration as a JSON string ({"enableCredential": boolean, "enableInstrumentation": boolean}). If not provided, local email/password authentication is enabled and OpenTelemetry instrumentation is disabled by default. |
Email is optional. Leave ACTIVITIES_EMAIL and all individual
ACTIVITIES_EMAIL_* variables unset to disable email intentionally. In that
mode, account registration keeps the no-email behavior and notification sends
are skipped. Notification delivery is best effort: errors are logged, with no
durable retries or delivery-status tracking.
ACTIVITIES_EMAIL accepts the full provider configuration as JSON and takes
precedence when it is syntactically valid. If the JSON is malformed,
configuration falls back to the individual variables below; a syntactically
valid value with an unsupported provider or schema is rejected rather than
falling back. Unknown providers, including the removed lambda provider, fail
configuration instead of silently disabling email. Before upgrading an
instance that used Lambda, migrate its configuration to SMTP, Resend, or AWS
SES and remove the Lambda-specific variables. The application does not choose
a replacement provider automatically.
When configuring email through individual variables rather than JSON,
ACTIVITIES_EMAIL_TYPE is required. A partial configuration without a
provider type is invalid and fails configuration; leave all email variables
unset when email should be disabled.
| Variable | Description |
|---|---|
ACTIVITIES_EMAIL |
Full email configuration as a JSON string. |
ACTIVITIES_EMAIL_TYPE |
Email provider: smtp, resend, or ses. |
ACTIVITIES_EMAIL_FROM |
Sender email address (e.g., noreply@your-domain.tld). |
| Variable | Description |
|---|---|
ACTIVITIES_EMAIL_SMTP_HOST |
SMTP server hostname. |
ACTIVITIES_EMAIL_SMTP_PORT |
SMTP server port (e.g., 587). |
ACTIVITIES_EMAIL_SMTP_USER |
SMTP username. |
ACTIVITIES_EMAIL_SMTP_PASSWORD |
SMTP password. |
ACTIVITIES_EMAIL_SMTP_SECURE |
Use TLS (true or false). |
| Variable | Description |
|---|---|
ACTIVITIES_EMAIL_RESEND_TOKEN |
Resend API token. |
| Variable | Description |
|---|---|
ACTIVITIES_EMAIL_SES_REGION |
AWS region for SES (e.g., us-east-1). |
Optional. Enables POST /api/v1/statuses/:id/translate and the Translate control on posts, and sets translation.enabled in /api/v2/instance. One backend is active at a time, selected by ACTIVITIES_TRANSLATION_TYPE; if the required variables for the chosen backend are missing, translation is disabled. Translations are sanitized and cached in the translation_cache table.
| Variable | Description |
|---|---|
ACTIVITIES_TRANSLATION_TYPE |
Translation backend: deepl, gemini, or openai. |
ACTIVITIES_TRANSLATION_API_KEY |
API key. Required for deepl, gemini, and openai. |
ACTIVITIES_TRANSLATION_ENDPOINT |
Backend endpoint URL. Required for openai (full chat-completions URL including the path, e.g. https://api.openai.com/v1/chat/completions); optional for gemini (defaults to Google Generative Language API). |
ACTIVITIES_TRANSLATION_MODEL |
Model name. Required for openai (e.g. gpt-4o-mini); optional for gemini (defaults to gemini-2.5-flash). |
ACTIVITIES_TRANSLATION_PLAN |
DeepL plan: free (default) or pro. Routes requests to api-free.deepl.com or api.deepl.com. Used by deepl only. |
Optional. Automatically generates accessibility descriptions (alt text) for uploaded images and videos when no description is provided by the client, and generates route descriptions for fitness activity route maps, using an OpenAI-compatible vision chat-completions API. For a video, the representative preview frame is extracted and sent as the image input. Alt-text generation is best-effort: a provider or extraction failure leaves the media without a generated description rather than failing the upload or fitness processing. Note this is separate from storing the video's poster frame, which a synchronous video upload still requires to be decodable. If any required variable is missing, alt text generation is disabled.
| Variable | Description |
|---|---|
ACTIVITIES_ALT_TEXT_ENDPOINT |
Chat-completions endpoint URL supporting vision (e.g. https://api.openai.com/v1/chat/completions). Required. |
ACTIVITIES_ALT_TEXT_API_KEY |
API key for the chat-completions endpoint. Required. |
ACTIVITIES_ALT_TEXT_MODEL |
Model name supporting vision (e.g. gpt-4o-mini). Required. |
Required for media uploads (images and video in posts). If no media storage is configured, media uploads are disabled.
ACTIVITIES_MEDIA_STORAGE_PATH, ACTIVITIES_MEDIA_STORAGE_BUCKET, and ACTIVITIES_MEDIA_STORAGE_REGION are required for their storage type and have no default. Surrounding whitespace is trimmed, and a value that is set but empty (or whitespace only) is treated as not configured: the app logs a warning and leaves media storage disabled instead of starting a broken backend. An empty ACTIVITIES_MEDIA_STORAGE_PATH would otherwise resolve to the application's working directory, and an empty bucket or region would only fail later inside the AWS SDK.
If you have been running with a blank media path, treat it as a credential exposure, not merely a misconfiguration. GET /api/v1/files/<path> reads straight off the storage root with no database lookup — it only checks that the path stays inside the root and that the extension maps to a known MIME type — so with the root set to the working directory, any file in the application checkout with a recognised extension was readable without authentication. .env, .env.local and *.sqlite3 happen not to map to a MIME type and so were not readable, but that is not an all-clear; plenty of secret-bearing files do map, including:
- a legacy root
config.json(application/json), which on older instances held the whole configuration:secretPhase, database credentials, and auth and email secrets (S3 credentials were never in it — those come from the AWS SDK chain); backups/production-archives/*.tar.gz(application/gzip), the default output directory ofscripts/backup/productionArchive.ts— a full database and media archive;backups/actor-archives/*.tar.gz(application/gzip), the default output directory ofscripts/backup/exportActorArchive.ts— one account's full ActivityPub archive (every status regardless of visibility, media, fitness files, likes, bookmarks, and follows);- key material such as
.p8(the Apple Maps signing key),.pem,.crt,.p12and.pfx; .conf,.ini,.tomland.txt, plus source files (.ts,.js,.json,.md,.yml,.sql).
Audit what was sitting in the application directory and rotate anything reachable from that list, rather than assuming only source code leaked.
Leaving one of them unset while its storage type is configured is different, and still a hard startup failure — unless another required variable for the same storage type is blank, which disables storage before validation runs.
The rule covers those three value variables, not ACTIVITIES_MEDIA_STORAGE_TYPE, which is neither trimmed nor blank-checked: a padded ' fs ' is simply an unrecognised type. An unrecognised type — and a type left unset while other ACTIVITIES_MEDIA_STORAGE_* variables are set — disables media storage with a warning naming the variable.
| Variable | Description |
|---|---|
ACTIVITIES_MEDIA_STORAGE_TYPE |
Storage backend: fs (local), s3, or object (S3-compatible). |
ACTIVITIES_MEDIA_STORAGE_PATH |
Local filesystem path for fs storage (e.g., ./uploads). |
ACTIVITIES_MEDIA_STORAGE_BUCKET |
S3 bucket name (for s3 or object). |
ACTIVITIES_MEDIA_STORAGE_REGION |
S3 region (e.g., us-east-1). |
ACTIVITIES_MEDIA_STORAGE_HOSTNAME |
Public media hostname/CDN used to serve stored media files. If unset, media files are served through the app from the configured storage backend. This value is not used for S3 API operations. |
ACTIVITIES_MEDIA_STORAGE_ENDPOINT |
S3-compatible API endpoint used for storage operations and browser presigned uploads (for services like MinIO, DigitalOcean Spaces, Cloudflare R2). If unset, the AWS SDK uses the standard AWS S3 endpoint for the configured region; set this for non-AWS S3-compatible providers. |
ACTIVITIES_MEDIA_STORAGE_MAX_FILE_SIZE |
Maximum file size in bytes (default: 200 MiB / 209715200). Also pins the admin-editable media.maxFileSize setting; when unset, an admin can set the cap anywhere from 1 byte up to 1 GiB (1073741824). |
ACTIVITIES_MEDIA_STORAGE_QUOTA_PER_ACCOUNT |
Per-account combined media + fitness storage quota in bytes. If unset, the config value stays empty and the quota service applies its 1 GiB (1073741824) default when enforcing quota. |
S3 credentials are not ACTIVITIES_* variables: the AWS SDK resolves them from its standard chain, so set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (or leave them unset when the host already supplies an IAM role or instance profile).
Upgrade note: If you previously set
ACTIVITIES_MEDIA_STORAGE_HOSTNAMEorACTIVITIES_FITNESS_STORAGE_HOSTNAMEto a MinIO, Cloudflare R2, DigitalOcean Spaces, or other S3-compatible API endpoint, move that value to the matching*_STORAGE_ENDPOINTvariable.*_STORAGE_HOSTNAMEis for a public hostname/CDN origin, not for S3 API operations or browser presigned uploads.
Upgrade note: An instance that was running with a blank required storage variable (most likely
ACTIVITIES_MEDIA_STORAGE_TYPE=fswith an emptyACTIVITIES_MEDIA_STORAGE_PATH=) had a working-looking media backend rooted at the process working directory. That configuration now disables media and fitness storage: uploads stop, and every existing media URL serves the "media removed" placeholder. The signal is a pair of boot warnings —ACTIVITIES_MEDIA_STORAGE_PATH is set but empty; media storage will be disabledand the matching… fitness storage will be disabled— so grep foris set but emptyafter upgrading. To recover, set a real path and move your existing files into it. They are loose in the directory the app was started from, named<16 hex characters><extension>:.webpfor stored images,-thumbnail.webpfor their thumbnails,.jpgfor the JPEG renditions generated for email, and.mp4/.webmfor video attachments — so move all of those, not just the.webpfiles. Fitness activity files were not loose in that directory: the fallback defaulted the empty path touploads, so move./uploads/fitness/into<new path>/fitness/as well, or every.fit/.gpx/.tcxand imported Strava.ziparchive goes missing. Route maps and their email JPEGs are not in there — they go through media storage, so they are among the loose files and the first move already covers them. Set the path even if you do not want media storage: leaving theACTIVITIES_MEDIA_STORAGE_*variables unset entirely is the supported way to turn uploads off.
For fitness activity file uploads (.fit, .gpx, .tcx). Falls back to media storage configuration if not set.
ACTIVITIES_FITNESS_STORAGE_PATH, ACTIVITIES_FITNESS_STORAGE_BUCKET, and ACTIVITIES_FITNESS_STORAGE_REGION follow the same rule as their media counterparts: whitespace is trimmed, and a set but empty value disables fitness storage with a warning rather than starting a broken backend. The media-storage fallback applies the rule to the ACTIVITIES_MEDIA_STORAGE_* variables it reads, so a blank ACTIVITIES_MEDIA_STORAGE_PATH no longer falls through to uploads/fitness under the application's working directory.
Setting ACTIVITIES_FITNESS_STORAGE_TYPE to a non-empty value opts out of the media-storage fallback. If the type is set but unusable — a blank required value, or a type this app does not recognise — fitness storage is disabled and a warning names the variable; it does not quietly write fitness files into the media bucket. (Leaving the type empty, ACTIVITIES_FITNESS_STORAGE_TYPE=, still selects the fallback, exactly as leaving it unset does.)
| Variable | Description |
|---|---|
ACTIVITIES_FITNESS_STORAGE_TYPE |
Storage backend: fs, s3, or object. |
ACTIVITIES_FITNESS_STORAGE_PATH |
Local filesystem path for fs storage. Required when ACTIVITIES_FITNESS_STORAGE_TYPE=fs is set (there is no default). When fitness storage is not explicitly configured and media storage is fs, fitness files fall back to <ACTIVITIES_MEDIA_STORAGE_PATH>/fitness and this variable is ignored. |
ACTIVITIES_FITNESS_STORAGE_BUCKET |
S3 bucket name. |
ACTIVITIES_FITNESS_STORAGE_REGION |
S3 region. |
ACTIVITIES_FITNESS_STORAGE_HOSTNAME |
Public fitness file hostname/CDN. If fitness storage is not explicitly configured, it inherits ACTIVITIES_MEDIA_STORAGE_HOSTNAME through the media-storage fallback; if explicit fitness storage is configured and this is unset, no separate fitness public hostname is configured and files are served through the app. This value is not used for S3 API operations. |
ACTIVITIES_FITNESS_STORAGE_ENDPOINT |
S3-compatible API endpoint used for fitness storage operations and browser presigned uploads. If fitness storage is not explicitly configured, it inherits ACTIVITIES_MEDIA_STORAGE_ENDPOINT through the media-storage fallback; otherwise, if unset, the AWS SDK uses the standard AWS S3 endpoint for the configured region. Set this for non-AWS S3-compatible providers. |
ACTIVITIES_FITNESS_STORAGE_PREFIX |
S3 key prefix (default: fitness/). |
ACTIVITIES_FITNESS_STORAGE_MAX_FILE_SIZE |
Maximum file size in bytes (default: 50 MiB / 52428800). |
ACTIVITIES_FITNESS_STORAGE_QUOTA_PER_ACCOUNT |
Override for the shared per-account media + fitness storage quota in bytes. When set, it takes precedence over ACTIVITIES_MEDIA_STORAGE_QUOTA_PER_ACCOUNT for both media and fitness quota checks; when unset, the media quota or quota service default applies. |
ACTIVITIES_FITNESS_MAPBOX_ACCESS_TOKEN |
Mapbox API token for map rendering. Required when ACTIVITIES_FITNESS_MAP_PROVIDER=mapbox (an empty token falls back to osm). It also selects mapbox when ACTIVITIES_FITNESS_MAP_PROVIDER is unset or holds an unknown value. Only public pk.* tokens are passed to browser maps; secret sk.* tokens stay server-side and the browser falls back to osm. |
ACTIVITIES_FITNESS_MAP_PROVIDER |
Fitness map provider: apple, mapbox, or osm. When unset or set to an unknown value the provider is inferred for back-compat: mapbox if ACTIVITIES_FITNESS_MAPBOX_ACCESS_TOKEN is set, otherwise osm. If the selected provider is missing its required credentials, it falls back to keyless osm so maps keep rendering. Changing the provider requires an app restart because the resolved CSP is cached for the process lifetime. |
ACTIVITIES_FITNESS_APPLE_MAPS_TEAM_ID |
Apple Developer team ID used to sign Apple Maps (MapKit JS) tokens. Required when ACTIVITIES_FITNESS_MAP_PROVIDER=apple; if this, the key ID, or the private key is blank the provider falls back to osm. |
ACTIVITIES_FITNESS_APPLE_MAPS_KEY_ID |
Apple MapKit JS key ID (Key ID of the MapKit private key). Required when ACTIVITIES_FITNESS_MAP_PROVIDER=apple; if this, the team ID, or the private key is blank the provider falls back to osm. |
ACTIVITIES_FITNESS_APPLE_MAPS_PRIVATE_KEY |
Apple MapKit JS private key (PEM). Required when ACTIVITIES_FITNESS_MAP_PROVIDER=apple; if this, the team ID, or the key ID is blank the provider falls back to osm. A single-line \n-escaped PEM is accepted and expanded into a real multi-line key. |
ACTIVITIES_FITNESS_ROUTE_HEATMAP_MEMORY_BUDGET_BYTES |
Worker heap budget before route-cache accumulation is downsampled (default: 512 MB). |
ACTIVITIES_FITNESS_ROUTE_HEATMAP_ACCUMULATION_POINT_LIMIT |
Maximum in-memory route points before accumulation is downsampled (default: 160,000). |
ACTIVITIES_FITNESS_ROUTE_HEATMAP_FILE_POINT_LIMIT |
Maximum points retained from one parsed fitness file before privacy trimming (default: 80,000). The trim's along-route distance is therefore measured on the already-decimated track, which under-estimates true route length and so trims slightly more, never less. |
ACTIVITIES_FITNESS_ROUTE_HEATMAP_SIMPLIFY_TOLERANCE_METERS |
Finest Ramer–Douglas–Peucker tolerance (meters) applied to each route before accumulation and to the final stored payload; straight stretches collapse toward their endpoints while bends keep road-following detail. Acts as a floor — dense regions are adaptively coarsened from here to fit the point budget without corner-cutting, and it is also the floor under each zoom's own one-pixel tolerance when the tile pyramid is built. Smaller = higher fidelity and larger payloads (default: 1). |
Interactive maps (route detail, heatmaps) and the stored static route-map images are
rendered by one pluggable provider, selected with ACTIVITIES_FITNESS_MAP_PROVIDER:
apple— Apple Maps: MapKit JS in the browser (authenticated with a short-lived token signed fromACTIVITIES_FITNESS_APPLE_MAPS_TEAM_ID/..._KEY_ID/..._PRIVATE_KEY) and the Apple Maps Snapshots API for static images. Both draw Apple's muted standard basemap — the standard road map with its land, water and POI colours de-saturated — so the route lines, heat runs and privacy circles painted on top stay the most prominent thing on the map. There is no map-type control and no way to switch to hybrid or satellite.mapbox— Mapbox GL JS plus the Mapbox Static Images API, usingACTIVITIES_FITNESS_MAPBOX_ACCESS_TOKEN.osm— keyless MapLibre GL JS with OpenFreeMap tiles; no credentials needed.
Fallback rule: if the selected provider's credentials are incomplete (any required
variable blank — for apple that means all three of team ID, key ID and private key),
the resolver silently falls back to osm rather than failing to render. Only presence is
checked, so a malformed private key still selects apple and surfaces as a failed token
mint. When the variable is unset or holds an unknown value the provider is inferred: a
configured Mapbox token selects mapbox for backward compatibility, otherwise osm.
Because the Content-Security-Policy
is resolved once and cached for the process lifetime, changing the provider (or its
credentials) requires restarting the app.
Apple Maps free tier (per Apple Developer Program membership, as published by Apple): 250,000 MapKit JS map views per day, 25,000 service calls per day, and 25,000 unique snapshot requests per day. Static route images are generated once per fitness post and stored, so snapshot usage tracks new imports rather than page views.
Existing route-map images keep the style they were generated with — both the
provider and its basemap; run Regenerate maps for old statuses on the fitness
privacy page (/fitness/privacy, backed by
POST /api/v1/fitness/general/regenerate-maps) after switching providers, or after
a basemap change, to re-render them.
For asynchronous processing of ActivityPub delivery, file processing, etc.
| Variable | Description |
|---|---|
ACTIVITIES_QUEUE_TYPE |
Queue backend: database, qstash, or cloudtasks. |
ACTIVITIES_QUEUE_DATABASE_MAX_RETRIES |
Maximum retry attempts for database queue tasks using polynomial backoff before capturing failed task in dead letter queue (default 16). |
ACTIVITIES_QUEUE_DATABASE_POLL_INTERVAL_MS |
Polling interval in milliseconds for the in-process database queue runner (default 1000). |
ACTIVITIES_QUEUE_URL |
Full callback endpoint URL of this instance's queue handler, e.g. https://your-domain.tld/api/v1/queue/qstash or https://your-domain.tld/api/v1/queue/cloudtasks. The queue backend delivers job messages to exactly this URL — it is not a base URL. For Cloud Tasks, this also serves as the default audience for OIDC token verification when ACTIVITIES_QUEUE_CLOUDTASKS_AUDIENCE is unset. |
ACTIVITIES_QUEUE_NAME |
Queue name for Cloud Tasks (e.g. activities-prod-queue). |
ACTIVITIES_QUEUE_TOKEN |
QStash API token. |
ACTIVITIES_QUEUE_CURRENT_SIGNING_KEY |
QStash current signing key (for webhook verification). |
ACTIVITIES_QUEUE_NEXT_SIGNING_KEY |
QStash next signing key (for key rotation). |
ACTIVITIES_QUEUE_QSTASH_MAX_RETRIES |
Maximum retry attempts before delivering failed task to QStash DLQ (default 3). |
ACTIVITIES_QUEUE_CLOUDTASKS_LOCATION |
Google Cloud location/region for Cloud Tasks (default europe-west1). |
ACTIVITIES_QUEUE_CLOUDTASKS_PROJECT_ID |
Google Cloud project ID for Cloud Tasks (falls back to FIREBASE_PROJECT_ID or default credentials project if unset). |
ACTIVITIES_QUEUE_CLOUDTASKS_SERVICE_ACCOUNT |
Authorized Google Cloud service account email for Cloud Tasks OIDC authentication. Required for OIDC verification; an audience alone never authorizes requests. |
ACTIVITIES_QUEUE_CLOUDTASKS_AUDIENCE |
Expected audience for Cloud Tasks OIDC token verification (falls back to ACTIVITIES_QUEUE_URL if unset). |
ACTIVITIES_QUEUE_CLOUDTASKS_SECRET |
Pre-shared webhook secret for Cloud Tasks endpoint authentication (Authorization: Bearer <secret>, x-cloudtasks-secret, or x-cloudtasks-token). |
ACTIVITIES_QUEUE_CLOUDTASKS_MAX_RETRIES |
Maximum retry attempts before capturing failed task in dead letter queue (default 5). |
| Variable | Description |
|---|---|
ACTIVITIES_PUSH_VAPID_PUBLIC_KEY |
VAPID public key for Web Push. |
ACTIVITIES_PUSH_VAPID_PRIVATE_KEY |
VAPID private key for Web Push. |
ACTIVITIES_PUSH_VAPID_EMAIL |
VAPID contact email, often mailto:.... |
| Variable | Description |
|---|---|
ACTIVITIES_ALLOW_MEDIA_DOMAINS |
Optional JSON array of additional service-owned media origins allowed by runtime img-src and media-src CSP. Use this for media created by this service and served from public domains/CDNs that are not otherwise covered by the configured media storage hostname. This setting is additive and does not restrict browser-loaded federated remote media. |
ACTIVITIES_ALLOW_REMOTE_MEDIA_DOMAINS |
Optional JSON array of remote media origins allowed by runtime img-src and media-src CSP for browser-loaded federated status images, avatars, emoji, video, and audio. Next image optimization is disabled and remote image patterns are build-time static, so this setting is enforced at request time instead of during next build. When unset or blank, federated remote media defaults to broad HTTPS browser loading; when set, broad https: is replaced by the configured origins. Set [] to block all federated remote media sources. This governs federated media only: img-src always keeps https://i.ytimg.com, the thumbnail host for the click-to-play YouTube player in a link preview card, because that is a fixed host belonging to a first-party feature rather than a remote origin an operator is choosing to trust. |
ACTIVITIES_ALLOW_ACTOR_DOMAINS |
JSON array of allowed domains for actors. |
ACTIVITIES_FEDERATION_MODE |
Federation mode: open (default) or allowlist to require explicit allowed actor domains. |
ACTIVITIES_ENABLE_INBOX_FORWARDING |
Enable ActivityPub Outbound Inbox Forwarding (W3C ActivityPub §7.1.2) to fan out verified public replies and mentions targeting local users to their remote followers (true or false, default: false). |
ACTIVITIES_TRUSTED_HOSTS |
JSON array of additional public hosts accepted from X-Forwarded-Host and X-Activity-Next-Host. |
| Variable | Description |
|---|---|
ACTIVITIES_REQUEST_TIMEOUT |
HTTP request timeout in milliseconds for outgoing federation requests. |
ACTIVITIES_REQUEST_RETRY |
Number of retries for failed outgoing requests. |
ACTIVITIES_REQUEST_RETRY_NOISE |
Random delay noise added between retries (in milliseconds). |
ACTIVITIES_REQUEST_MAX_RESPONSE_SIZE_BYTES |
Maximum size of an outgoing federation request's response body, in bytes (default: 2 MB). Larger responses are rejected. |
| Variable | Description |
|---|---|
OTEL_SERVICE_NAME |
OpenTelemetry service name. The app itself does not read this variable or set a default — it is only meaningful to an externally attached OTel SDK/collector. |
OTEL_EXPORTER_OTLP_ENDPOINT |
OpenTelemetry collector endpoint URL. |
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
Trace-specific OTLP endpoint URL. |
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT |
Metrics-specific OTLP endpoint URL. |
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT |
Logs-specific OTLP endpoint URL. |
OTEL_EXPORTER_OTLP_PROTOCOL |
OTLP protocol: grpc, http/protobuf, or http/json. The app config schema also accepts the non-standard value google; it stores openTelemetry.protocol as google and does not require an endpoint. |
OTEL_EXPORTER_OTLP_HEADERS |
OTLP headers string passed to the exporter. |
LOG_LEVEL |
Logger level, default info. |
| Variable | Description |
|---|---|
NODE_ENV |
Node.js environment (development or production). |
BUILD_STANDALONE |
Set to true to build a standalone Next.js output (used in Docker). |
NEXT_TELEMETRY_DISABLED |
Set to 1 to disable Next.js telemetry. |