Web service for Phobius, a combined transmembrane topology and signal peptide predictor, and for PolyPhobius, its homology-supported variant.
This replaces the Perl CGI server that ran from 2004. The prediction engines are
unchanged — the labels this service returns are byte-for-byte identical to those
of the legacy engine, and tests/test_golden.py enforces that — but everything
around them has been rebuilt as a single container.
docker run -p 8000:8000 -v /path/to/licensed:/mnt/data:ro ghcr.io/statisticalbiotechnology/phobius:latestThe mounted directory must contain phobius.model. See Licensed files below.
| Path | What it is |
|---|---|
app/fasta.py |
FASTA parsing and residue normalisation |
app/features.py |
label strings → feature tables and topology strings |
app/engines.py |
subprocess wrappers around the prediction engines |
app/homology.py |
BLAST+ and Kalign pipeline for PolyPhobius |
app/plot.py |
posterior probability plot, rendered as inline SVG |
app/models.py |
request validation and submission limits |
app/main.py |
FastAPI routes and templates |
start-script.sh |
container entry point; Serve runs this from the WORKDIR |
scripts/build_phobius_bundle.sh |
builds the academic download bundle |
engine/ |
homologhmm.jar (GPL), biojava.jar (LGPL) |
tests/golden/ |
reference output captured from the legacy engine |
Nothing licensed is in the container image, which is what lets the image be published freely.
| File | Terms | Where it lives |
|---|---|---|
phobius.model |
academic licence | mounted at /mnt/data, PHOBIUS_MODEL |
decodeanhmm |
licensed executable | optional, not required — see below |
homologhmm.jar |
GPL | in the image |
biojava.jar |
LGPL | in the image |
The Java engine covers every prediction mode, so decodeanhmm is not needed.
It is about 8× faster for a single sequence (41 ms versus 356 ms, almost all of
it JVM startup), but only ~1.4× on throughput, so on a web form the difference is
imperceptible. Most deployments should skip it.
If you do want it, put all three files on the same mounted storage:
/home/data/phobius.model # required
/home/data/phobius.options # required by decodeanhmm; looked for beside the model
/home/data/decodeanhmm # the licensed binary, chmod +x
/home/data/phobius.env # PHOBIUS_DECODEANHMM=/home/data/decodeanhmm
phobius.options is looked for next to the model, not next to the binary;
PHOBIUS_OPTIONS overrides that.
The historical binary will not work. It is a 32-bit build and the image is 64-bit with no i386 libraries, so it cannot be executed at all. Rebuild it for 64-bit from the anhmm source first.
Nothing here can break the service. At startup the binary is probed with a real prediction, and if anything is wrong — missing, not executable, wrong architecture, missing options file — the reason is logged and the Java engine is used instead:
WARNING phobius: native fast path requested but unusable, falling back to the
Java engine: '/home/data/decodeanhmm' could not be executed (No such file or
directory). The historical build is 32-bit and cannot run in this 64-bit image;
rebuild it for 64-bit from the anhmm source.
The golden tests assert both engines produce byte-identical output, so switching between them cannot change results.
| Variable | Default | Meaning |
|---|---|---|
PHOBIUS_MODEL |
discovered | full path to the licensed model; must be named phobius.model |
PHOBIUS_DATA_DIR |
unset | directory searched before the defaults |
PHOBIUS_ENGINE_DIR |
/app/engine |
directory holding the jars |
PHOBIUS_BLAST_DB |
discovered | BLAST database prefix; absent disables that mode |
PHOBIUS_DECODEANHMM |
unset | optional licensed native engine |
PHOBIUS_MAX_SEQUENCES |
100 |
sequences per request |
PHOBIUS_MAX_RESIDUES |
50000 |
residues per request (~20 s of compute) |
PHOBIUS_MAX_RESIDUES_SINGLE |
10000 |
residues in one sequence |
PHOBIUS_ENGINE_TIMEOUT |
120 |
seconds before an engine is killed |
PHOBIUS_MAX_CONCURRENCY |
2 |
concurrent predictions; match the CPU allocation |
The model must keep the filename phobius.model: the Java engine loads it as a
classpath resource by that exact name, not as an argument.
You normally do not need to configure anything. Mount the storage holding
phobius.model anywhere, and it is found by searching, in order:
PHOBIUS_MODEL, if set — a full path, and always winsPHOBIUS_DATA_DIR, if set/mnt/data,/home/data,/data,/app/dataengine/phobius.modelbeside the source, for local development
Each mount is searched both directly and in a db/ sub-directory, so a tidy
layout works without any configuration:
/home/data/phobius.model
/home/data/phobius.options # only needed with the native engine
/home/data/decodeanhmm # optional
/home/data/phobius.env # optional
/home/data/db/swissprot.* # the nine BLAST database files
/home/data/download/*.tar.gz # bundles offered on the download page
The same search locates the BLAST database, keying off swissprot.pin (or
swissprot.pal for a split database) and using its prefix. Since that is nine
files, /home/data/db/ is the tidier home for it and is searched by default. The image deliberately does not
pin PHOBIUS_MODEL, because an explicit value beats discovery and would break
every deployment whose storage is mounted somewhere else.
If the model cannot be found, the container exits with a message listing every path it tried and what each directory actually contains, which is usually enough to spot a wrong mount path or a misnamed file.
SciLifeLab Serve provides no way to set environment variables for a custom app.
Any setting can instead go in a phobius.env file placed on the mounted storage,
alongside the model — start-script.sh loads it at boot:
# phobius.env
PHOBIUS_MODEL=/home/data/licensed/phobius.model
PHOBIUS_MAX_SEQUENCES=25
PHOBIUS_MAX_CONCURRENCY=4Only KEY=VALUE lines are honoured; anything else is ignored rather than
executed, so the file cannot be used to run arbitrary commands from the volume.
PolyPhobius works two ways. Supplying your own aligned FASTA needs no database and is the reproducible option. The automatic search needs a BLAST database on the mounted storage:
./scripts/build_swissprot_db.sh /home/data/dbThat downloads Swiss-Prot and produces ~340 MB of swissprot.* files, well
inside the 5 GB volume cap. /home/data works too — both are searched. The option appears in the PolyPhobius form by itself
once the database is readable.
-parse_seqids is required and the script passes it: without it the search still
runs but blastdbcmd cannot retrieve the full subject sequences the aligner
needs. A database missing it is reported at startup rather than failing per
request.
The original server searched UniProt/TrEMBL, so predictions from this path will not reproduce historical ones.
DIAMOND is the faster tool for bulk searches, but its cost for a single query is dominated by building a seed index over the whole database. Measured against Swiss-Prot on this image, one query:
| time | homologues found | |
|---|---|---|
DIAMOND --very-sensitive |
42.8 s | 7 |
DIAMOND default, -c1 |
16.0 s | 5 |
| blastp | 1.2 s | 8 |
blastp is 36× faster and returned a strict superset. End to end the request went from 78 s to 1.25 s. The database location makes no difference — reading it from the mounted volume cost 16.0 s versus 17.5 s from container-local disk, so this is compute, not I/O.
.github/workflows/ci.yml runs the tests, then builds the image, then verifies
it, and only then pushes to ghcr.io/<owner>/phobius.
Serve never re-fetches a tag it has already deployed, so :latest will not
update a running app — the platform requires a new, unique tag for every update
and there is no restart button. Each push to master therefore publishes a
date-and-sha tag, e.g. :20260819-1f07289, and the workflow run's summary prints
the exact reference to paste into the app's Settings → Image. :latest is
published only for local docker pull.
A v* git tag additionally publishes semver tags, which are equally valid to
deploy:
git tag v2.0.0 && git push --tags # -> :2.0.0, :2.0, :latestThree checks run against the built image before anything is pushed, because a licensed file that reached a public registry could not be un-published:
| Check | Fails if |
|---|---|
| licensed files | phobius.model, phobius.options or decodeanhmm is in the image |
| runtime user | the image does not run as UID 1000 |
| start-up script | ./start-script.sh is missing or not executable at the WORKDIR |
| smoke test | the container does not boot and serve /healthz, /, /docs |
The smoke test launches the container the way Serve does — overriding the
entrypoint and invoking ./start-script.sh by path — rather than relying on the
ENTRYPOINT, because those two paths can differ and only the first is what
actually runs in production.
The smoke test uses a placeholder model, so CI never needs the licensed file.
On the first push the GHCR package is private. Serve can only pull public images, so go to Packages → phobius → Package settings → Change visibility → Public once. Later pushes keep that setting.
Build provenance attestation is attempted only when the repository itself is public — GitHub requires a public repo (or a paid plan) to persist attestations, and the step runs after the image is pushed, so failing it would mark a successful publish as broken. It starts working on its own once the repository is made public.
The image is amd64 only. To also build arm64, add
platforms: linux/amd64,linux/arm64 to the push step — expect a much longer
build, since the arm64 layers are emulated.
The image is built for the platform's constraints: it runs as UID 1000,
listens on port 8000 (Serve allows 3000–9999), starts through an executable
./start-script.sh at the WORKDIR (/app), which is how Serve launches a
container, holds no licensed or private data, and needs no persistent volume
unless you enable the homology search.
- Publish the image and make the GHCR package public, as above. To deploy an update afterwards, paste the new tag from the workflow summary into Settings → Image and press Update — reusing a tag does nothing.
- In your project, define a storage mount and place
phobius.modelthere — Serve's file storage is not public, so the licensed model stays private. Any mount path works;/home/dataand/mnt/dataare found automatically, and anything else can be pointed to with aphobius.envfile on the same storage. - Create an app of type Other, point it at
ghcr.io/<owner>/phobius:latest, set the port to 8000, and select the mount path. GET /healthzreturns 503 with a specific message if the model is missing. Without it the container refuses to start rather than failing per-request.
Default resources (2 vCPU, 4 GB) are ample. If you raise them, raise
PHOBIUS_MAX_CONCURRENCY to match.
The download page offers the standalone Phobius distribution. It contains
phobius.model and decodeanhmm, so like the model it is licensed and never
part of the image — it is served from download/ on the mounted storage:
./scripts/build_phobius_bundle.sh ~/bin /home/data/downloadThat produces a ~530 KB phobius-1.01.tar.gz holding phobius.pl, the decoder,
the model, the decoder options and a README with the licence terms. The page
lists whatever is present; with nothing there it points at the licensing page
instead.
Only .tar.gz, .tgz, .zip and .pdf files are offered, and the route serves
files matched against a scan of the directory rather than joining the requested
name onto a path — so a stray phobius.env on the volume cannot be fetched, and
traversal is impossible by construction rather than by filtering.
Do not distribute the old web-server tree as "Phobius". A
phobius.tar.gzcontaininghtml/,cgi-bin/andprogram/is a snapshot of the retired server: 90 MB of obsolete JREs and vendored BioPerl, including the legacypredict.plwhose FASTA-header handling allowed command injection.
phobius.sbc.su.se has been the published URL since 2004 and appears in cited
methods sections. See docs/legacy-redirect.md for how
to point it at this service — including why a 308 rather than a 301 matters for
scripts that still POST to /cgi-bin/predict.pl.
POST /api/predict with {"sequence": "<FASTA>"} returns structured
predictions; add "plot": true for an SVG per sequence. POST /api/predict.txt
returns the feature table as plain text, ?format=short for one line per
protein. Interactive documentation is at /docs.
The legacy POST /cgi-bin/predict.pl endpoint still works with its original
field names, so existing bookmarks and scripts keep running.
pip install -e ".[dev]"
PHOBIUS_MODEL=engine/phobius.model uvicorn app.main:app --reload
pytestTests that need an engine skip cleanly when the model is absent. pytest also
skips the homology tests unless ncbi-blast+ and kalign are on PATH; the
container has both.
tests/golden/labels.tsv holds labels captured from the legacy engine for a
52-sequence corpus covering edge cases (1 residue, 3000 residues, all-hydrophobic,
ambiguous residues, hostile FASTA headers). If a change makes those tests fail,
the change altered predictions. Regenerate them only with
tests/generate_golden.py, and only against the legacy engine.
Two deliberate quirks are preserved for byte-compatibility and are covered by
tests: a two-region prediction reports a non-cytoplasmic loop as CYTOPLASMIC.,
and the short-format header retains its original spelling. Both are noted in the
code.
Käll L, Krogh A, Sonnhammer ELL. A combined transmembrane topology and signal peptide prediction method. J Mol Biol 338:1027–1036 (2004).
Käll L, Krogh A, Sonnhammer ELL. An HMM posterior decoder for sequence feature prediction that includes homology information. Bioinformatics 21(Suppl 1):i251–i257 (2005).