dragoman
A web server for scholarly metadata with full DOI Resolution and 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 COMMONMETA_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.
Database file not found
If the resolved database path does not exist, dragoman logs a warning and starts without a local database, falling back to the live API for all requests:
WARN dragoman: sqlite file not found, running without local database path=…
To use a local database, place the SQLite file at the platform default path or set COMMONMETA_DB to its location.
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: COMMONMETA_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. |
COMMONMETA_DB |
(platform default) | Path to a local commonmeta SQLite3 database. Metadata is served from the database before falling back to the live API. If the file does not exist, a warning is logged and the server starts without a local database. Platform defaults: macOS → ~/Library/Application Support/commonmeta/commonmeta.sqlite3; Linux → /var/lib/commonmeta/commonmeta.sqlite3. |
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
RIS
CSL (Citeproc) JSON
Crossref
Crossref XML
DataCite
Schema.org JSON-LD
InvenioRDM
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:
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+json |
crossref |
|
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.
Place the database
The platform default path on macOS is ~/Library/Application Support/commonmeta/commonmeta.sqlite3. Place the database there and no further configuration is needed:
To use a different path, set COMMONMETA_DB in the launchd plist or pass --db on the command line.
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 platform default path on Linux is /var/lib/commonmeta/commonmeta.sqlite3. Place the database there and no further configuration is needed:
To use a different path, set COMMONMETA_DB in the environment file instead.
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