dragoman
A web server for scholarly PID (Persistent Identifier) resolution with full DOI content negotiation. Send a DOI as the URL path; receive a redirect to the landing page or metadata in any supported format depending on the Accept header.
Installation
Prerequisites
- Rust 1.75+ (rustup.rs)
Install
This builds a release binary and installs it to ~/.cargo/bin/dragoman. Make sure ~/.cargo/bin is on your PATH (the Rust installer adds this automatically).
Local SQLite database
dragoman can serve metadata directly from a local SQLite database in the commonmeta format, bypassing the live Crossref/DataCite APIs. This dramatically reduces latency and API load for high-traffic deployments.
Database format
The database is a SQLite3 file with a single works table whose columns map one-to-one to the commonmeta v1.0 schema. The id column is the canonical DOI URL (e.g. https://doi.org/10.5281/zenodo.1234). Complex fields (contributors, references, …) are stored as JSON text.
You can build a database from any commonmeta-supported source using the commonmeta CLI.
Running the server
Start
# Default port 3456
# Custom port
# With a local database
# Write a PID file so the process can be stopped later
# All options together
Options can also be supplied as environment variables (flags take precedence):
PORT=8080 DRAGOMAN_DB=/data/commonmeta-2026-06-15.sqlite3 RUST_LOG=dragoman=debug
During development you can use cargo run in place of the installed binary:
# Run from the project root — the sqlite3 file in the root is loaded by filename
# Or with a full path
Error: port already in use
If the chosen port is already in use, the server logs an error and exits:
ERROR dragoman: failed to bind port=3456 error=Address already in use (os error 48)
Choose a different port with --port or stop the process that holds the port.
Error: database file not found
If --db points to a path that does not exist, the server exits at startup before accepting any traffic:
ERROR dragoman: failed to open database path=… error=sqlite file not found: '…'
Pass the correct path or an absolute path to avoid working-directory ambiguity.
Stop
# Stop using the default PID file location (/tmp/dragoman.pid)
# Stop using a custom PID file
dragoman stop sends SIGTERM to the running process. The server handles the signal gracefully: it finishes in-flight requests and removes the PID file before exiting. Pressing Ctrl-C has the same effect.
CLI reference
dragoman <COMMAND>
Commands:
start Start the server (runs in the foreground)
stop Stop a running server by sending SIGTERM to its PID file
help Print help
dragoman start [OPTIONS]
-p, --port <PORT> TCP port to listen on [env: PORT] [default: 3456]
-d, --db <PATH> Local commonmeta SQLite3 database [env: DRAGOMAN_DB]
--pid-file <PATH> Write PID to this file on startup [env: DRAGOMAN_PID_FILE]
dragoman stop [OPTIONS]
--pid-file <PATH> PID file to read [env: DRAGOMAN_PID_FILE] [default: /tmp/dragoman.pid]
Environment variables
| Variable | Default | Description |
|---|---|---|
PORT |
3456 |
TCP port to listen on. |
DRAGOMAN_DB |
(none) | Path to a local commonmeta SQLite3 database. Metadata is served from the database before falling back to the live API. The server exits on startup if the path is set but the file cannot be opened. |
DRAGOMAN_PID_FILE |
(none) | Path for the PID file written by start and read by stop. |
RUST_LOG |
dragoman=info |
Log filter (see tracing-subscriber). Use dragoman=debug to log per-request cache hits. |
Usage
Redirect (HTML / browser)
When the Accept header prefers text/html or is absent, dragoman redirects to the DOI's landing page:
# Follow the redirect
# Inspect the redirect target without following
# https://zenodo.org/record/1089100
Content negotiation
Send an Accept header to receive metadata instead of a redirect.
BibTeX
CSL-JSON
DataCite JSON
RIS
Crossref XML
Schema.org JSON-LD
Formatted citation
text/x-bibliography accepts optional style= and locale= parameters. Style names come from the CSL style repository; locale codes from the CSL locales repository.
# APA (default)
# Vancouver in French
Query parameter overrides
Use ?format= instead of an Accept header:
Force a specific registration agency (useful for testing):
Supported formats
| Accept header | ?format= value |
Notes |
|---|---|---|
application/x-bibtex |
bibtex |
|
text/x-bibliography |
citation |
style= and locale= params |
application/vnd.commonmeta+json |
commonmeta |
|
application/vnd.crossref.unixref+xml |
crossref_xml |
|
application/vnd.crossref.unixsd+xml |
crossref_xml |
alias |
application/vnd.citationstyles.csl+json |
csl |
|
application/vnd.datacite.datacite+json |
datacite |
|
application/vnd.datacite.datacite+xml |
datacite_xml |
|
application/vnd.inveniordm.v1+json |
inveniordm |
|
application/x-research-info-systems |
ris |
|
application/vnd.schemaorg.ld+json |
schemaorg |
|
text/html / (absent) |
— | 307 redirect to landing page |
HTTP status codes
| Code | Meaning |
|---|---|
| 200 | Metadata returned |
| 307 | Redirect to landing page |
| 404 | DOI not found |
| 406 | Requested content type not supported |
| 502 | Upstream API error |
Deployment (macOS)
Installation via Homebrew
dragoman can be installed from the front-matter Homebrew tap:
This builds dragoman from source (requires Rust, installed automatically as a build dependency) and places the binary at $(brew --prefix)/bin/dragoman.
Recommended SQLite path
$(brew --prefix)/var/dragoman/commonmeta.sqlite3
Which resolves to:
/opt/homebrew/var/dragoman/commonmeta.sqlite3— Apple Silicon/usr/local/var/dragoman/commonmeta.sqlite3— Intel
Place the database
Run as a background service (launchd)
# Start at login and keep alive
# Check status
# View logs
# Stop the service
brew services start installs a launchd plist in ~/Library/LaunchAgents/ and starts the service immediately. It restarts automatically on crash and at login.
To run as a system-level daemon (starts at boot, not tied to a user login), use sudo brew services start dragoman. This installs the plist in /Library/LaunchDaemons/ instead.
Configuration
To change the port or other settings, edit the service environment variables and restart:
# Open the generated plist for editing
Manual installation (without Homebrew)
# Install Rust if not already installed
|
Intel Macs: replace
/opt/homebrewwith/usr/localin all paths below.
Run as a launchd daemon
The bundled com.front-matter.dragoman.plist targets Apple Silicon paths.
Check logs:
Stop and disable:
Updating
With Homebrew
Manual
Deployment (Debian / systemd)
This section covers running dragoman as a persistent system service on a Debian 13 server.
1. Build the binary
On the server, install Rust and install the binary:
|
Or cross-compile locally and copy the binary:
# macOS → Linux x86-64 (requires cross)
2. Create system user and directories
3. Place the SQLite database
The recommended database path is /var/lib/dragoman/commonmeta.sqlite3:
4. Create the environment file
5. Install and enable the systemd unit
Check the service is running:
Updating the binary
Updating the database
The database file can be replaced while the service is running. dragoman opens the SQLite file once at startup; to pick up a new file, restart the service:
Reverse proxy
Caddy (standalone)
Caddy is the recommended reverse proxy for standalone deployments. It handles TLS certificates automatically via Let's Encrypt.
|
|
&&
Add a site block to /etc/caddy/Caddyfile:
doi.example.com {
reverse_proxy localhost:3456
}
Reload Caddy:
Traefik via Coolify
If the server already runs Coolify, its Traefik instance can route directly to the dragoman systemd service. Add a file-provider config to the Coolify dynamic configuration directory:
Traefik picks up the file automatically — no reload needed. host.docker.internal resolves to the host from inside the Traefik container; dragoman must listen on all interfaces (0.0.0.0:3456, which is the default).
License
MIT