In short
A server instance deploys from one Docker Compose file and a secret key you generate. Two services are enough — the application and the web front —, the database creates itself, and migrations apply on every start. For a real hostname two settings need changing: the allowed origin and the TLS certificate, which Linkr does not provide.
Two ways to deploy
Server mode
Docker, on a machine at your institution.
User accounts, shared storage, server-side code execution, git versioning. This page is about that.
Static site
Files dropped on any static host.
Everything runs in the browser. No accounts, no shared storage, no git versioning — see Deployment modes.
Starting an instance
One file is all it takes. It is self-contained: no clone, no build.
curl -O https://framagit.org/interhop/linkr/linkr/-/raw/main/docker/docker-compose.hub.yml
export LINKR_SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(48))")
docker compose -f docker-compose.hub.yml up
Then open http://localhost:3000: the setup wizard takes it from there.
The secret key: generated once, kept forever
It signs login tokens and encrypts the passwords of registered databases. Without it the server refuses to start — deliberately. Losing it makes those secrets unreadable: back it up as carefully as your data.
What exactly it protects and what rotating it implies: Configuration.
What the file starts
Two services, and nothing else to install.
| Service | Role | Port |
|---|---|---|
| web | The interface, served by nginx, which also relays calls to the application. | 3000 |
| api | The application: data, code execution, git. | 8000 |
The browser only ever talks to port 3000: calls go through /api/ and are relayed internally. Port 8000 therefore does not need exposing, and you can remove its publication from the file.
No database service
By default the data fits in a SQLite file stored with everything else. There is no PostgreSQL container to run and no database password to choose. For sustained multi-user use PostgreSQL remains possible by changing the database address — see Configuration.
The setup wizard
On first start the application notices that no account exists and shows a three-step wizard.
Database
Shows, read-only, the engine and location actually in use. The database is configured server-side and created automatically; to change it, edit the configuration and restart.
Administrator account
Username and password for the first administrator. This step is only possible while no account exists.
Default data
Offers to install a demo workspace — an OMOP database, mappings, an ETL pipeline, example projects. Needs network access to the catalog; with no network the instance starts empty and the wizard still finishes.
No requirement is enforced on the administrator password
The application only checks that both entries match. The strength of that password — the most privileged account on the instance — is entirely up to you.
Moving to a real hostname
The shipped file is set up for localhost. Two things need handling before exposing the instance.
The allowed origin
The application only accepts calls from declared origins. While LINKR_CORS_ORIGINS is http://localhost:3000, an instance served on any other name will have all its calls blocked by the browser. Declare the real public origin:
- LINKR_CORS_ORIGINS=https://linkr.example.org
A wildcard is refused at startup
Setting * makes the launch fail, with an explicit message. This is not an annoyance to work around: since calls carry session credentials, a wildcard would let any website query your instance on behalf of your users.
TLS encryption
Linkr does not terminate HTTPS. The web container listens in the clear on port 80; putting a TLS terminator in front of it — nginx, Traefik, HAProxy, or your institution’s reverse proxy — and managing the certificates there is your job.
If you front it with your own proxy, two details matter
WebSockets must be relayed on /api/ too, not only on /ws/: the execution terminal opens its connection under /api/. A proxy that upgrades only /ws/ yields a terminal that never connects.
Timeouts must be long. An R or Python run and a large query go well past the 60-second default; the shipped proxy waits up to an hour.
Maximum upload size: two ceilings, the lower one wins
The shipped proxy accepts request bodies up to 512 MB, while the application allows 2 GB. So 512 MB is what applies. To take larger files, raise the proxy’s limit — or better, avoid the upload: put the file on the server and point at it by path, see Files on the server.
Where the data lives
Everything your users create — the database and every file — sits in one folder, mounted into the container. The images hold no data of their own.
By default it is a named Docker volume: it survives restarts and upgrades, but Docker owns it and it is awkward to copy. To keep it in a real folder, replace the volume line:
volumes:
- /srv/linkr:/root/.linkr
A mounted folder is never deleted by accident
docker compose down -v destroys a named volume; a mounted folder is never touched. That is the second reason to prefer one for anything you care about.
What that folder contains in detail is in Configuration, and what actually needs copying in Backup and restore.
Upgrading
Versions are pinned on purpose: a latest would let an update move the application under a running instance without anyone asking for it.
To upgrade, change both image lines to the new version, then restart:
docker compose -f docker-compose.hub.yml pull
docker compose -f docker-compose.hub.yml up -d
Both lines change together
The two images stamp the same format version into exports: a mismatched pair writes exports that disagree about their own format.
Back up first, because rolling back is not symmetrical
Schema migrations apply automatically on start. Going back to the previous image does not undo them: an upgraded instance does not come back down by a mere version change. The backup taken before the upgrade is your only real rollback.
Migrations are never run by hand
They apply on every start and do nothing when the schema is already current. No command is to be run during an upgrade.
Going further
- Configuration — every variable, and what it changes.
- Backup and restore — what to copy, and in which order to restore.
- Authentication and permissions — creating accounts after the wizard.
- Deployment modes — what the choice of mode changes.