In short
An instance is configured through environment variables, all prefixed LINKR_. Three really matter: the secret key, the data directory and the allowed origin. The rest tune code execution, AI models and package repositories. Settings editable inside the application are a separate matter, covered at the end.
The three that matter
Everything else has a sensible default. These do not.
LINKR_SECRET_KEY=<a long random string> # required
LINKR_DATA_DIR=/var/lib/linkr # this is what you back up
LINKR_CORS_ORIGINS=https://linkr.example.org # the real public address
LINKR_DEBUG=false
LINKR_DEBUG must stay false in production
This is not merely a convenience setting: debug mode is the only thing that allows starting with the example secret key or with a wildcard origin. It also changes the log format, from structured JSON to a human-readable console rendering.
The secret key
It does two jobs at once, which is what explains the care it deserves.
It signs login tokens
Changing it logs everyone out immediately. Account passwords are untouched: nobody is locked out, everyone signs in again.
It encrypts secrets at rest
The encryption key is derived from it: there is no second secret to manage, but changing one changes the other.
Four things are encrypted with it: the passwords of registered databases, the IDE connection secrets, each user’s git access tokens, and the API keys of AI models.
Losing the key breaks nothing loudly — and that is the trap
Decryption fails silently rather than with an error. The instance starts, the interface works, but every database password, git token and API key becomes unreadable and has to be re-entered by hand. Back this key up as carefully as you back up the data.
The data directory
One variable names the place where everything is stored. The folder is created on first start.
| Contents | What it is |
|---|---|
linkr.db | The application database, when you declare no other: accounts, projects, metadata. |
_files/ | Files, stored by fingerprint: datasets, attachments, imports. Two identical files are stored once. |
projects/ | One working folder per project: scripts and datasets, under their real names. |
workspaces/, mapping-projects/… | The git working copies of each entity linked to a repository. |
.cache/ | R and Python package caches, shared by all projects. Rebuildable: it can be left out of a backup. |
_tmp/ | Uploads in flight. Cleaned automatically. |
Never two instances on the same folder
The disk is authoritative, with no mapping table: two instances sharing a data directory would tread on each other.
An automatic sweep runs at startup and every six hours: abandoned uploads, files nothing references any more, folders of deleted projects.
The application database
By default, a SQLite file stored in the data directory. For sustained multi-user use, PostgreSQL is recommended:
LINKR_DATABASE_URL=postgresql+asyncpg://linkr:password@localhost:5432/linkr
This choice is made at install time, not after
Changing engine on a running instance does not move the data: the instance would start from an empty database. That is why the interface does not offer switching to PostgreSQL — see below.
Code execution
This is the most sensitive part, and the most adjustable.
LINKR_ENABLE_CODE_EXECUTION=false # turns execution off across the instance
This switch overrides permissions
When it is false, nobody runs code, whatever their rights. The IDE, terminals, package environments and the server folder picker are all refused with an explicit message. Pointing a database at a path still works: attaching read-only data is not running code.
The limits
| Variable | Default | What it bounds |
|---|---|---|
| LINKR_EXECUTION_TIMEOUT_SECONDS | 120 | One interactive run. The clock restarts on each line of output: a chatty script is not cut off. |
| LINKR_JOB_TIMEOUT_SECONDS | 1800 | One background job. Unlike the previous one, this is a hard limit. |
| LINKR_MAX_KERNELS_PER_USER | 5 | Concurrent processes per user — code sessions and terminals together. |
| LINKR_SESSION_TIMEOUT_MINUTES | 60 | Idle time before a code session closes. The user’s login is untouched. |
| LINKR_MAX_BUILD_CONCURRENCY | 2 | Simultaneous long-running server work: environment builds and background jobs share this budget. |
| LINKR_MAX_UPLOAD_MB | 2048 | The size of one upload. The shipped proxy caps lower — see Production install. |
Package repositories
Useful on a closed network, or to enforce an institutional mirror:
LINKR_PIP_INDEX_URL=https://pypi.internal.example/simple
LINKR_R_REPOS=https://cran.internal.example
The default R repository serves binaries
It points at a mirror distributing packages already compiled for Linux. A plain CRAN mirror would make the server compile each package, which lengthens environment builds considerably.
AI models
The assistant can talk to a local or a remote model, and the distinction is a governance decision.
LINKR_ALLOW_REMOTE_LLM=false # the default
A remote model means prompts — which can carry clinical context — leave the institution. Permission is therefore something to grant explicitly, not to withdraw.
Two independent locks, deliberately
The instance switch answers “does the institution allow egress?”. A second confirmation, per declared provider, answers “is someone taking responsibility?” — whoever declares a remote model must tick a sentence acknowledging that data leaves the institution, and their name is recorded. The two protect against different things.
The models themselves are declared per workspace, in the interface, with their address and API key. Keys are encrypted and never sent back to a browser. Any OpenAI-compatible address works — a local model served on the machine, or a hosted service.
Internal addresses are filtered
The server refuses to call certain reserved or service addresses, which stops a declared provider being used to probe the internal network from inside the application.
What is set inside the application
Not everything goes through variables. The Settings page carries several tabs, each protected by a permission:
- General — information about the application database.
- Organizations, Users, Roles — see Authentication and permissions.
- Catalog — the repository the catalog is read from, to point at an internal catalog. See Community catalog.
- Backup & sync — import, export and version accounts, roles and organizations.
The General tab does not reconfigure the server
The database form it shows is a display convenience kept in your browser: it does not change the database the instance uses. Only LINKR_DATABASE_URL does, followed by a restart. PostgreSQL is deliberately not offered there, since changing engine mid-life does not bring the data along.
Each workspace also has its own settings — members, badges, environments, AI assistant — described in Workspace settings.
Logs and monitoring
In production, logs are emitted as structured JSON on standard output: collect them with your platform’s log collector. In debug mode they switch to a human-readable console rendering.
What does not exist
There is no email sending — so no password reset by mail and no invitations: an administrator sets passwords by hand —, no log file or rotation to configure, and no metrics exposed. To watch the instance you have only the /api/v1/health address, which answers if the server is alive.
Going further
- Production install — where to put these variables.
- Backup and restore — what the data directory implies.
- Files on the server — bounding what the picker reaches.
- Authentication and permissions — the rights that open these screens.