Skip to content

Configuration

.env.example lives at the repository root — start from it: .env.example.

Where the actual .env goes depends on how you run the app. Docker Compose reads it from the repository root, next to the compose file. Native runs — migrations, node ace, the dev server — expect it inside apps/webapp, since that's where the node ace CLI itself resolves it from:

bash
cp .env.example apps/webapp/.env

Required variables

VariableNotes
NODE_ENVdevelopment, production, or test
PORTPort the application listens on, e.g. 3333
APP_KEYApplication secret key — generate with openssl rand -base64 32
HOSTIP address or hostname, e.g. 0.0.0.0 or localhost
LOG_LEVELe.g. info, debug
APP_URLPublic application URL, e.g. https://your-domain.com
DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_DATABASEPostgreSQL connection

Optional variables

VariableNotes
TZTimezone, e.g. UTC
ALLOW_REGISTRATIONWhether the instance accepts sign-ups — see Authentication
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRETGoogle sign-in — see Authentication
SMTP_* / MAIL_FROM_*Outgoing mail — see below

Favicon store

The application caches favicon images on disk instead of re-fetching them on every visit, at a fixed path (storage/favicons under the app root — /app/storage/favicons in the Docker image, persisted via the favicon-volume volume, see Docker & compose). Nothing to configure.

This is a cache, not user data: losing it just means the next visit to each site re-downloads its favicon. It's still worth keeping in your backup strategy for one reason — restoring it avoids a fresh round of outbound requests to every domain your instance has ever bookmarked, all at once, right after a restore.

That leads to the actual thing to know before self-hosting this: resolving a favicon fetches it from the site itself, from your instance's server. On a single-user instance — the common case here — that means the site operator sees your instance's IP request its own favicon at the moment you bookmark it, which is a weaker anonymity property than on a shared multi-user instance where the same request is one among many users' traffic. No third-party fallback provider is contacted unless you explicitly configure one (a future setting, opt-in and disabled by default).

Google sign-in

To obtain a Client ID and Secret:

  1. Open the Google Cloud Console and create or select a project.
  2. Enable the OAuth 2.0 API.
  3. Configure the OAuth consent screen if you have not already.
  4. Under Credentials, create an OAuth 2.0 Client ID of type Web application.
  5. Add an authorized redirect URI: http://localhost:3333/auth/callback for development, or https://your-domain.com/auth/callback in production.
  6. Set the resulting Client ID and Secret as GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.

Leave both empty to run without Google sign-in. Setting only one of the two is rejected at boot.

Outgoing mail

Email is optional. Leave every SMTP_* and MAIL_* variable empty and the instance runs without it — the behaviors below are simply unavailable, and account recovery goes through the node ace user:* commands instead (see Console commands).

Setting any one of them commits you to a complete configuration: SMTP_HOST and MAIL_FROM_ADDRESS are then both required, and a partial configuration is rejected at boot rather than silently dropping the one email a locked-out user is waiting for.

VariableNotes
SMTP_HOSTRelay hostname
SMTP_PORTDefaults to 587
SMTP_USERNAMEOptional, but requires SMTP_PASSWORD when set
SMTP_PASSWORDOptional, but requires SMTP_USERNAME when set
SMTP_SECUREImplicit TLS. Defaults to true on port 465, false elsewhere
MAIL_FROM_ADDRESSSender address
MAIL_FROM_NAMESender name, defaults to MyLinks

What outgoing mail changes

Four behaviors switch on or off depending on whether mail is configured:

BehaviorWithout outgoing mailWith outgoing mail
Signing in with a passwordNo confirmation requiredA confirmed address is required; the login page offers to resend the link
/forgot-passwordUnavailable — reset with node ace user:reset-passwordSends a self-service reset link
Changing your email addressUnavailable from the settings page — use node ace user:*Two-party confirmation: new address confirms, old address is notified
Admin-issued password resetConsole only: node ace user:reset-password --linkThe admin area can email the reset link directly

In development

dev.compose.yml ships mailpit: point SMTP_HOST at 127.0.0.1 and SMTP_PORT at 1025, then read everything the app sends on http://localhost:8025. Nothing leaves your machine.

In production

An external SMTP provider is the recommended path. The repository's compose.yml does carry a local boky/postfix relay behind an opt-in profile:

bash
docker compose --profile smtp up -d

with SMTP_ALLOWED_SENDER_DOMAINS set to the domains it may send for, SMTP_RELAYHOST set if it should forward to an upstream, and the app configured with SMTP_HOST=smtp and SMTP_PORT=587.

Be aware of what self-hosting a mail transfer agent involves: without SPF, DKIM, DMARC records and a matching reverse DNS entry, Gmail and Outlook drop the mail, and many hosting providers block outbound port 25 outright.

Released under the AGPLv3 License.