Linkr
Home Resources Tools Documentation Blog Demo
FR
  • What is Linkr?
  • Deployment modes
  • Quick start
  • Local install
  • With Docker
  • Manual install
  • Client-only
  • Install LibreChat
  • Your first project
  • Linkr in a clinical data warehouse
  • Workspaces and projects
  • The data pipeline
  • Entities and sharing
  • Versioning and collaboration
  • Overview
  • Projects
  • Wiki
  • Plugins
  • Members and roles
  • Settings
  • Schemas
  • Getting and exploring
  • Mapping
  • Databases
  • Derived sub-databases
  • Data quality
  • Data catalog
  • Build and publish
  • Anonymize
  • SQL script collections
  • ETL pipelines
  • Building and running
  • Generating the scripts
  • Overview
  • Mapping projects
  • Global view
  • Target concepts
  • Mapping editor
  • Suggestions
  • AI agent
  • Evaluation
  • Export
  • Overview
  • Databases
  • Concepts
  • Cohorts
  • Building
  • Results, SQL and report
  • Patient data
  • Pipeline
  • Datasets
  • IDE
  • Web apps
  • Versioning
  • Overview
  • Tabs and widgets
  • Built-in widgets
  • Analysis widgets
  • Control charts (SPC)
  • Surveys and eCRF
  • R and Python code
  • Filters, settings and export
  • Overview
  • Presentation mode
  • Exporting a report
  • Agents
  • MCP server
  • Skills
  • Import and export
  • Git versioning
  • Community catalog
  • Publishing content
  • Production install
  • Configuration
  • Database connectors
  • Authentication and permissions
  • Files on the server
  • Backup and restore
  • Contributing code
  • Glossary
  • Keyboard shortcuts
  • Release notes
Documentation Administration Configuration

Configuration

An instance's environment variables: the two keys, the data directory, code-execution limits, package repositories, and the access log.

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, package repositories and how long the access log is kept. Settings editable inside the application are a separate matter, covered at the end.

Client Not available in client-only mode — this feature requires a backend. Backend Available with the FastAPI backend.

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 two keys

Linkr holds two, and mixing them up is the costliest mistake on this page: one only signs logins, the other seals stored secrets. Neither is derived from the other.

LINKR_SECRET_KEY

Signs login tokens

Where it lives
An environment variable, required: without it the server refuses to start.
If it changes
Everyone is logged out and signs in again. Nothing else is lost.
If it is lost
Same effect: generate a new one.

Encryption key

Seals stored secrets

What it seals
Remembered database passwords, IDE connection secrets, git tokens.
Where it lives
LINKR_ENCRYPTION_KEY if set; otherwise secret.key, in the data directory.
If it is lost
Those secrets become unreadable, with no error: everyone has to re-enter them.
LINKR_SECRET_KEY can be changed at the cost of logging everyone out. The encryption key cannot be lost without losing the secrets it seals.

The encryption key seals three things: the database passwords each user chooses to remember, the IDE connection secrets and the git access tokens. Each secret is sealed together with what it protects — the user, the database and its address —, so a secret copied into another row no longer opens. Account passwords are not encrypted but hashed: neither key concerns them.

Losing the encryption 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 remembered database password, git token and connection secret becomes unreadable and has to be re-entered by hand. That is what happens when the application database is restored without the secret.key file.

Keeping the encryption key outside the folder

By default there is nothing to do: secret.key is created on first use, readable by the server alone, and a backup of the data directory takes it along with the rest. If your policy wants keys in a secrets vault rather than next to the data, supply it yourself:

# 32 random bytes, "url-safe" base64
LINKR_ENCRYPTION_KEY=$(python3 -c "import secrets, base64; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())")

For an instance already running, reuse the contents of secret.key rather than a fresh key: a fresh key would make the existing secrets unreadable.

Changing the encryption key

Put the new key in LINKR_ENCRYPTION_KEY and the old one in LINKR_ENCRYPTION_OLD_KEYS — several old keys are separated by commas. Each secret records which key sealed it: old ones stay readable, new ones are sealed with the new key.

Do not remove an old key from the list

Only database passwords are re-sealed under the new key, the next time they are used. Git tokens and IDE connection secrets stay sealed with the old one: removing it would make them unreadable.

The data directory

One variable names the place where everything is stored. The folder is created on first start.

Back upRebuildableTemporary
LINKR_DATA_DIR
linkr.db
The application database (SQLite, by default): accounts, projects, metadata.Back up
secret.key
The key sealing stored secrets, generated on first use — unless LINKR_ENCRYPTION_KEY is set.Back up
_files/
Files, stored by fingerprint: datasets, attachments, imports.Back up
_databases/
DuckDB databases Linkr created (from a schema, derived from a cohort).Back up
projects/<uid>/
One working folder per project: scripts, datasets, environment specs.Back up
.cache/envs/
The materialised R and Python environments, rebuilt from their spec.Rebuildable
workspaces/, mapping-projects/…
The git working copies of entities linked to a repository.Back up
audit/
The access log: one file per day, compacted per month.Back up
.cache/
R and Python packages shared by every project.Rebuildable
_cache/, _global_table_cache/
Cached query results.Rebuildable
_duckdb_ext/
DuckDB extensions, downloaded again when needed — which takes network access.Rebuildable
_tmp/
Uploads in flight, cleaned automatically.Temporary
The data directory, entry by entry. Everything tagged “Back up” must be copied — secret.key included; the rest rebuilds or cleans itself. The switch greys out what a backup can leave behind.

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

VariableDefaultWhat it bounds
LINKR_EXECUTION_TIMEOUT_SECONDS120One interactive run. The clock restarts on each line of output: a chatty script is not cut off.
LINKR_JOB_TIMEOUT_SECONDS1800One background job. Unlike the previous one, this is a hard limit.
LINKR_MAX_KERNELS_PER_USER5Concurrent processes per user — code sessions and terminals together.
LINKR_SESSION_TIMEOUT_MINUTES60Idle time before a code session closes. The user’s login is untouched.
LINKR_MAX_BUILD_CONCURRENCY2Simultaneous long-running server work: environment builds and background jobs share this budget.
LINKR_BUILD_TIMEOUT_SECONDS1800One package-environment build. Without it, a hung build would hold its place in the previous budget forever.
LINKR_WIDGET_POOL_SIZE2R or Python processes kept warm, per language and per project, so code widgets render without waiting for an interpreter to start.
LINKR_WIDGET_MAX_CONCURRENCY8Code widgets running at once: a busy dashboard does not start one process per widget all at the same time.
LINKR_MAX_UPLOAD_MB2048The 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.

Other variables

VariableDefaultWhat it sets
LINKR_FS_BROWSE_ROOTSemptyThe folders the server file picker may browse — see Files on the server.
LINKR_AUDIT_RETENTION_DAYS365How long the access log, described below, is kept.
LINKR_PASSWORD_MIN_LENGTH12The shortest password a local account may set (at least 8). Also required: different from the username, at most 72 bytes, and not on the list of the 100,000 most common passwords.
LINKR_POOL_TTL_SECONDS300How long a connection to a database stays open between two queries before it is closed for inactivity.
LINKR_KERNEL_TOKEN_EXPIRE_MINUTES720The lifetime of the token the R and Python libraries receive in a code session. Short on purpose: any code run in the project can read it.
LINKR_AUTH_PROVIDERlocalThe authentication mode. local is the only value implemented — see Authentication and permissions.

What about AI models?

Linkr calls no model itself. Agents run in an external client — LibreChat, OpenCode… — that holds its own model configuration, and act on the instance through the MCP server, with a user’s API key and permissions. Choosing a local or a remote model, and so deciding whether data may leave the institution, happens in that client. See MCP server.

What is set inside the application

Not everything goes through variables. The Settings page carries six tabs, each protected by a permission — the tab stays visible, but its contents are replaced by a notice if you lack yours.

demo.linkr.interhop.org

Settings

Manage users and platform configuration.

Permissionapp-database:read

Application database

Shows, read-only, the engine and location of the database the server actually uses, and lets you query it.

The six Settings tabs. Click a tab to see what it holds and the permission that opens it; Access log exists in server mode only.

Organizations, users and roles are covered in Authentication and permissions, the catalogs — managed from the Catalog page itself — in Community catalog.

The General tab is read-only

It shows the database the instance actually uses: the absolute path of the linkr.db file in the data folder, or the host and name of the database LINKR_DATABASE_URL points at (never its password). To change it, edit LINKR_DATA_DIR or LINKR_DATABASE_URL and restart the server. Changing engine mid-life does not bring the data along.

Each workspace also has its own settings — members, badges, default environments — 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.

The access log

Every request that reads data, changes something or is refused leaves one line: who, when, from where, by which path — the interface, an API key, a code session, a background job —, on which database, with which SQL or code, and how many rows came back. Never the data itself. The SQL and code can still name a patient, so reading this log takes the audit-log:read permission.

A failed line also carries its reason: why a request was refused, or the database’s message when a connection fails (password hidden). Finally, every request gets a reference. When the application answers with a deliberately vague message, “Connection failed” or “Internal server error”, it adds that reference (“ref. 3f9a1c07be42”). It can be found in the tab’s Reference column and on every server-log line the same request produced, with the full detail, including an internal error’s stack trace.

It is written in two places at once:

  • In the data directory, under audit/: one file per day, compacted into one file per month. Months older than LINKR_AUDIT_RETENTION_DAYS are deleted.
  • On standard output, with the other logs: that is the copy your platform’s log collector keeps.

An edit shows

Each line carries a fingerprint that depends on the previous one: editing or removing a line breaks the chain from there on. The tab’s Check integrity button verifies it. This does not protect against someone holding administrator rights on the server who rewrites everything — the copy on standard output, out of their reach, is what covers that.

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 rotation to configure beyond the retention above, 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.
PreviousProduction installNextDatabase connectors

Product

  • Home
  • Demo

Resources

  • Documentation
  • Resources
  • Tools
  • Blog

Community

  • Framagit source code
  • Github source code

About

  • InterHop.org
  • Contact

2021–2026 InterHop — CC BY-NC-SA 4.0 (site) · AGPLv3 (software)