Versioned PseudoDojo ONCV stringent pseudopotential bundle, packaged as a FROM scratch Docker image. Ships UPF families for PBE and PBEsol with a manifest.json for runtime resolution (Quantum ESPRESSO), and a flat PBE psp8 set (ABINIT). Full periodic-table coverage where upstream provides it. The image name predates the second consumer.
The image content is just /pseudo/{pbe,pbesol}/<element>.{upf,djrepo}, /pseudo/psp8/<element>.psp8 and /pseudo/manifest.json — no shell, no libc, nothing else. Intended for COPY --from consumption by downstream QE runners that need a pinned, integrity-verified, manifest-indexed pseudopotential set.
SCAN is not bundled — PseudoDojo NC SR 0.4 doesn't publish a SCAN family. Users who need SCAN must supply their own bundle.
PseudoDojo distributes tarballs over HTTP. The advantages of repackaging as a Docker image are concrete:
- Reproducibility — image tags are immutable;
ghcr.io/material-codes/qe-pseudos:pseudodojo-v0.4is byte-identical forever - Integrity — every UPF's MD5 is cross-checked against its djrepo's
md5_upffield at build time, before publishing - Atomic consumption —
COPY --from=...:tag /pseudo /opt/pseudopulls everything in one Docker layer with caching, no shell scripting or curl-and-verify dance - Manifest — a single
manifest.jsonkeyed by family + element gives runtime resolvers an O(1) lookup with cutoffs and z_valence already parsed and unit-converted (Ha → Ry) - Sanity guard — the build re-derives
ecutwfcfor known elements (Si, O) and refuses to publish if the values don't match expectations within a tolerance, catching upstream metadata regressions before they hit production
Good fits:
- Workflow runners that resolve
<family> + <element>requests at job submission time (e.g. material/core'sqerunner) - CI fixtures that need a known-good pseudo set without downloading from
pseudo-dojo.orgon every run - Reproducibility artifacts for published QE calculations using PseudoDojo defaults
Not a fit:
- PAW-only workflows — these are norm-conserving (NC) only
- GBRV / SSSP / PSlibrary users — bundle a different image for those
- ABINIT users — ABINIT consumes
.psp8/.psmlformats, not the.upfshipped here. Use the PSP8 bundles from PseudoDojo directly for ABINIT - Workflows needing SCAN — supply your own SCAN bundle
/pseudo/
├─ manifest.json ← family + element index
├─ pbe/
│ ├─ <Element>.upf ← e.g. Si.upf, O.upf
│ └─ <Element>.djrepo ← per-element PseudoDojo metadata (cutoffs, hints, MD5)
├─ pbesol/
│ ├─ <Element>.upf
│ └─ <Element>.djrepo
├─ psp8/
│ └─ <Element>.psp8 ← flat PBE stringent set for ABINIT, 72 elements
└─ psp8.djson ← the upstream table the psp8 set was selected by
The psp8 set is not indexed by manifest.json: its consumer resolves a
pseudopotential by stat-ing <dir>/<Element>.psp8 and reads its cutoffs from
the job, so an index would be a second source of truth nobody queries. The
selection table is kept beside it for provenance — it names, per element, which
upstream variant was chosen and its md5.
Element coverage matches upstream PseudoDojo NC SR v0.4 stringent — most of the periodic table; check manifest.json after pulling for the exact list.
Field semantics:
schema_version— currently1. Bumped if the manifest shape changes incompatibly.families.<family>.functional— XC functional this family targets (PBE,PBEsol).families.<family>.subdir— directory under/pseudocontaining the UPF files.families.<family>.dual—ecutrho / ecutwfcratio. NC pseudos usedual=4.elements.<family>.<element>.file— UPF filename relative to the family subdir.elements.<family>.<element>.ecutwfc_ry— recommendedecutwfcin Rydberg, derived from PseudoDojo'shints.normal.ecut(the recommended production hint level), converted Ha → Ry.elements.<family>.<element>.ecutrho_ry—ecutwfc * dual.elements.<family>.<element>.z_valence— number of valence electrons, parsed from the UPFPP_HEADER.
If you change the schema here, downstream consumers (e.g. cmd/qerunner/pseudo.go in material/core) need updating to match.
docker pull ghcr.io/material-codes/qe-pseudos:pseudodojo-v0.4| Tag pattern | Meaning |
|---|---|
pseudodojo-v<N.M> (e.g. pseudodojo-v0.4) |
Pinned to a PseudoDojo release. Immutable. |
latest |
Tracks the most recent release tag. Moves over time. |
For reproducibility, always pin a specific version in production references.
FROM ghcr.io/material-codes/qe-base:7.4.1
# Stage the bundle into /opt/pseudo. The manifest is at /opt/pseudo/manifest.json.
COPY --from=ghcr.io/material-codes/qe-pseudos:pseudodojo-v0.4-r2 /pseudo /opt/pseudo
# An ABINIT runner wants only the flat psp8 half:
# COPY --from=ghcr.io/material-codes/qe-pseudos:pseudodojo-v0.4-r2 /pseudo/psp8 /opt/pseudodojoThe runner can then read /opt/pseudo/manifest.json at startup, build a family → element → ElementDef map, and resolve incoming jobs by looking up (requested_family, requested_element) directly.
A typical resolution flow on the consumer side:
- Read
manifest.jsononce at startup. - For each incoming job, look up the requested family. Reject if absent.
- For each species in the calculation, look up the element under that family. Reject if absent (per fail-fast policy).
- Use the manifest's
ecutwfc_ryandecutrho_ryas defaults; allow user override but warn if it sits below the recommended max across species. - Resolve
fileto the absolute path/opt/pseudo/<subdir>/<file>for the QE input deck.
docker build $(grep -E '^[A-Z0-9_]+=' pseudo-bundles.env | sed 's/^/--build-arg /') \
-t qe-pseudos:pseudodojo-v0.4-r2 .The character class must include 0-9: every SHA-256 variable and the whole
PD_PSP8_* group carry digits, and a [A-Z_] pattern drops them without a word.
The build is fast (~3 minutes): a Go test+build of the manifest builder (~30s), a pull of the digest-pinned prior image for the UPF/djrepo trees (~20s), a single manifest pass that re-verifies every element's UPF md5 against its djrepo (~5s), and the psp8 stage's per-element fetch from GitHub with md5 verification (~1 min).
If an md5 doesn't match, the build fails at the manifest pass rather than shipping a bundle nobody checked.
Not from the website, currently. The UPF and djrepo trees are inherited from the
previously published image (pseudodojo-v0.4, pinned by digest), because
pseudo-dojo.org goes unreachable for days at a time and has already failed a
downstream runner release that way — and unlike the psp8 set, there is no
content-addressed upstream to switch to: abinit/pseudo_dojo publishes psp8 and
djrepo, not UPF. Inheritance keeps the release path independent of that host.
The pins in pseudo-bundles.env stay as the provenance record of which tarballs
those bytes came from, and the curl-and-verify stage lives in this file's git
history — restore it when the site is back, or when the bundle actually bumps
(a bump has to download from the website anyway; see below). Integrity does not
rest on the inheritance: the manifest pass re-verifies each element's UPF
against the md5 in its djrepo.
PseudoDojo updates approximately yearly. A bump replaces inherited bytes with
downloaded ones, so it needs the curl-and-verify fetch stage back from git
history first.
- Visit pseudo-dojo.org; for each functional, download:
- The new
_upf.tgzbundle (NC SR ONCVPSP v0.4 stringent, UPF format) - The matching
_djrepo.tgz(same accuracy/version, djrepo format)
- The new
- Run
sha256sum <each-downloaded-file>to get the new checksums. - Update
pseudo-bundles.env— replace each URL, SHA-256, and exact filename. The variable layout is 12 vars total: 2 bundles × 3 fields × 2 functionals. Pass them back as build-args in.github/workflows/build.yml, which today ships only thePD_PSP8_*group. - Cut a new tag, e.g.
pseudodojo-v0.5. The GHA workflow publishes both the version-pinned image andlatest. ~3 minutes.
The psp8 set bumps separately and does not come from the website. Point
PD_PSP8_COMMIT at a newer commit of abinit/pseudo_dojo
and update PD_PSP8_COUNT if the stringent table gained or lost elements — the
build fails on a count mismatch rather than shipping a shorter periodic table
quietly. No checksum list to maintain: the table carries an md5 per element and
the build verifies every file against it.
Why the repository and not the tarball: the two carry identical bytes (checked
md5-for-md5 against the set running in production), but pseudo-dojo.org has
gone unreachable for days at a time and, in August 2026, failed a downstream
runner release along with its retry. A commit pin is content-addressed and does
not depend on any host staying up.
The trigger is tags: ['pseudodojo-*']. Unrelated tags would not fire the workflow.
Image tags mirror the PseudoDojo upstream version verbatim (e.g. PseudoDojo NC SR v0.4 → image tag pseudodojo-v0.4). A packaging change that adds no upstream version — a new format bundled from the same release, say — appends a bundle revision instead: pseudodojo-v0.4-r2. Consumers stamp this tag into their job records, so the suffix is what tells a reader that two differing tags may still hold identical physics. The pseudodojo- prefix disambiguates from any future bundle types (e.g. a separate gbrv-* or sssp-* set in this same image namespace, if added).
The Dockerfile, manifest builder, and CI in this repo are MIT-licensed (see LICENSE).
The published image contains PseudoDojo pseudopotential files (.upf and .djrepo), which are distributed by upstream under the Creative Commons Attribution 4.0 International License (CC-BY 4.0). The MIT license covers only the repo's tooling — not the bundled pseudopotential data.
When using the image in published work, please cite the PseudoDojo project per their citation guidance — typically the van Setten et al. 2018 paper.
{ "schema_version": 1, "families": { "pseudodojo_pbe": { "functional": "PBE", "subdir": "pbe", "dual": 4 }, "pseudodojo_pbesol": { "functional": "PBEsol", "subdir": "pbesol", "dual": 4 } }, "elements": { "pseudodojo_pbe": { "Si": { "file": "Si.upf", "ecutwfc_ry": 36, "ecutrho_ry": 144, "z_valence": 4 }, "O": { "file": "O.upf", "ecutwfc_ry": 92, "ecutrho_ry": 368, "z_valence": 6 }, // ... one entry per element }, "pseudodojo_pbesol": { /* ... */ } } }