The silent page awaits the waking dawn.
ENV PATH=/opt/conda/envs/noah/bin:/opt/conda/envs/colabfold/bin:/opt/conda/bin:/opt/noah/ppm3_code:${PATH}
RUN ${CONDA_DIR}/bin/conda create -y -n ${NOAH_ENV} -c conda-forge \
python=3.12 \
pip \
dssp=4.6.1 \
libmcfp=2.0.1 \
gsutil
RUN test -x /opt/conda/envs/${NOAH_ENV}/bin/mkdssp && \
ln -sf mkdssp /opt/conda/envs/${NOAH_ENV}/bin/dssp && \
/opt/conda/envs/${NOAH_ENV}/bin/mkdssp --version && \
/opt/conda/envs/${NOAH_ENV}/bin/dssp --versionsudo apt-get update
sudo apt-get install -y --no-install-recommends \
ca-certificates \
curl \
gnupg2
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
| sudo gpg --dearmor \
-o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
| sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
export NVIDIA_CONTAINER_TOOLKIT_VERSION=1.20.0-1
sudo apt-get install -y \
nvidia-container-toolkit=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
nvidia-container-toolkit-base=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
libnvidia-container-tools=${NVIDIA_CONTAINER_TOOLKIT_VERSION} \
libnvidia-container1=${NVIDIA_CONTAINER_TOOLKIT_VERSION}
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart dockerexport http_proxy="http://<PROXY_HOST>:<HTTP_PORT>"
export https_proxy="http://<PROXY_HOST>:<HTTP_PORT>"
export all_proxy="socks5://<PROXY_HOST>:<SOCKS_PORT>"docker build --progress=plain \
--build-arg HTTP_PROXY="$http_proxy" \
--build-arg HTTPS_PROXY="$https_proxy" \
--build-arg ALL_PROXY="$all_proxy" \
-t noah_gpcr .export OUTDIR=/path/to/noah_runs
export RUN_ID=v2r-inactive-ark1
mkdir -p "$OUTDIR/noah_run/$RUN_ID"
mkdir -p "$OUTDIR/colabfold_cache"
docker run -d --rm \
--gpus device=0 \
--name noah_v2r_ark1 \
-v "$OUTDIR/noah_run/$RUN_ID:/work/out" \
-v "$OUTDIR/colabfold_cache:/root/.cache/colabfold" \
noah_gpcr \
bash -lc 'cd /work/out && /opt/conda/envs/noah/bin/python /opt/noah/run_noah.py -n v2r -p /opt/noah/ppm3_code -d /opt/noah/database -m Inactive -f ARK1'
Intro
Yesterday I happened to read a paper by Kojima et al. just published in Nature Structural & Molecular Biology, titled Universal pipeline for high-resolution GPCR structure determination. The article zeroes in on a very practical problem in GPCR structure determination: inactive-state GPCRs — especially the antagonist-bound state — often lack a stabilizing binding partner such as a G protein, so you usually have to try different fusion positions in the TM5-ICL3-TM6 region over and over before finding a construct whose expression, stability, and rigidity are sufficient to support high-resolution cryo-EM reconstruction, which makes the experimental screening expensive.
The authors' solution consists of two complementary parts. NOAH first enumerates TM5-TM6 fusion constructs and predicts their structures with ColabFold, then checks in order whether the linker stays a continuous α-helix, the local pLDDT, the helix phase, and the distance between the fusion protein and the membrane boundary, narrowing a few hundred combinations down to a handful of candidates that are more worth validating experimentally. They then designed ARK1, a ~40 kDa de novo fusion partner with higher rigidity, as a fiducial marker to help cryo-EM particle alignment, and validated it across multiple Class A GPCRs and different ligand states.
This is not a push-a-button route to experimental structures — expression, purification, grid preparation, and cryo-EM are still required afterward, and the systematic validation so far is mostly limited to Class A GPCRs. Even so, for projects working on inactive-state GPCRs or antagonist complexes, or ones that have been stuck on fusion-construct screening for a long time, it could remove a good part of the upfront trial and error. So I decided to reproduce NOAH's computational pipeline first and see whether it can help my own or neighboring projects; this post records the problems encountered during deployment.
As a first step, I tried deploying NOAH on a Linux server with an NVIDIA GPU. NOAH orchestrates GPCRdb data, DSSP, PPM3, and ColabFold to screen candidate constructs. The project already ships a Dockerfile, and it looks like just three commands get it started:
The actual deployment was far less smooth. One after another, I hit DSSP dependency incompatibilities, Docker failing to use the NVIDIA GPU, unstable access to external resources from the server, and underdocumented runtime parameters. This post records the complete debugging process and the deployment method that ultimately worked and can be reused.
NOAH's current LICENSE only permits non-commercial research and educational use and explicitly forbids redistributing the code to third parties, in original or modified form, including uploading it to a public GitHub repository. I therefore can't share the modified content publicly; what this post shares is the deployment approach and the troubleshooting experience. If you run into similar problems, you can hand this post to an AI as context, and it should make your deployment smoother.
1. Build failure: unstable DSSP and libmcfp version combinations
The first time I ran docker build, the problem was in the DSSP environment: mkdssp and dssp could be found but wouldn't run correctly during version checks or when invoked by Biopython. In other words, the problem wasn't “DSSP not installed” — it was that the binary interface between DSSP and the underlying libmcfp was incompatible.
The original Dockerfile first creates NOAH's Conda environment and installs DSSP, then separately installs gsutil. The two independent Conda solves can update indirect dependencies that were already installed. At the same time, the original PATH puts the ColabFold environment ahead of the NOAH environment, which also increases the risk of calling programs or dynamic libraries from the wrong environment.
In the end, the fix had four parts:
The core of the changes is as follows:
2. Seeing the GPU on the host doesn't mean Docker can use it
The second problem was at the GPU layer. nvidia-smi running normally on the server only proves the NVIDIA driver is working; for Docker to expose the GPU to a container, you also need to install and configure the NVIDIA Container Toolkit.
You can first check which runtimes Docker currently knows about:
On Ubuntu or Debian, I followed the official NVIDIA Container Toolkit installation documentation to complete the setup. The version number below is the one listed in the official docs as of 2026-08-30: 1.20.0-1; for future deployments, first confirm the current version on the official page.
nvidia-ctk runtime configure modifies the host's /etc/docker/daemon.json, so Docker can invoke the NVIDIA runtime. After configuring, don't just look at docker info — actually start a temporary container to verify. NVIDIA's official test command is:
Only when this command prints GPU information inside the container is the GPU runtime truly configured. The corresponding official verification is described in Running a Sample Workload.
3. Passing a proxy into the Docker build
NOAH's image build needs to reach external services such as Conda, PyPI, GitHub, and Google Cloud Storage. When the server's network is restricted, you can first set the proxy in a terminal on the host:
These export commands only set the proxy for the current host shell and the regular processes it launches, which fixes network access for commands like git clone on the host. However, Docker doesn't automatically pass these environment variables into the Dockerfile's RUN environment during image builds. So you may see this: the host can clone the repository fine, but docker build's wget, Conda, pip, or gsutil still time out.
So when running the build, you also need to explicitly pass the proxy via --build-arg:
Here, $http_proxy, $https_proxy and $all_proxy are taken from the host's current shell and then passed by --build-arg to the build environment. Just replace the placeholders with your own proxy addresses. Docker already predefines HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and other proxy build arguments, so there's no need to add a permanent ENV; otherwise the proxy address may end up baked into the image configuration. See the Docker CLI proxy documentation for details.
4. Supplementing the README
The original README gives the minimal commands but doesn't systematically explain which parameters are required, what the defaults are, or where results are written. Copying the commands verbatim raises several questions: what happens if you don't pass -m? Is -f required? Does --reverse enable or disable reverse screening? You might not even know these parameters exist.
NOAH's core command can be summarized as:
The most important parameters are:
Parameter
Required
Default
Meaning
-n, --name
Yes
None
GPCR name; in database mode it must match the database directory name, e.g., v2r.
-p, --ppm
Yes
None
PPM3 code directory, which should contain immers.
-d, --database
Conditional
None
NOAH/GPCRdb database directory; when not using a database, provide both -s and -j.
-s, --structure
Conditional
None
Custom GPCR PDB structure.
-j, --json
Conditional
None
Residue annotations in GPCRdb residues/extended format.
-m, --mode
No
Inactive
Conformation state; only accepts Active or Inactive.
-f, --fp
No
ARK1
Fusion protein; options are ARK1, BRIL, or A2A_BRIL.
-r, --reverse
No
Reverse screening by default
The name is misleading: passing this option actually turns off the reverse helix-phase screening.
Another key point: NOAH has no separate --output parameter; results are written to the current working directory. Different targets, conformations, or fusion proteins should each use a different empty directory; rerunning directly in an existing results directory can fail because some files are created in exclusive mode.
Therefore, I recommend putting the target, conformation, and fusion protein into RUN_ID and explicitly passing -m and -f, instead of relying on the defaults:
Here I use --gpus device=0, exposing only GPU 0 to the container. It's more direct than “exposing all GPUs and then setting CUDA_VISIBLE_DEVICES=0”. Docker's official guidance on specifying GPUs is in GPU access.
Currently, NOAH calls ColabFold one construct at a time, so it won't automatically get near-linear speedup just because the server has multiple GPUs and the command uses --gpus all. If you haven't modified the scheduling logic yourself, allocating a single GPU per task is usually cleaner and makes it easy to spread different tasks across different GPUs.
Once the task is started in the background, you can check progress like this:
5. Why the modified repository isn't public
To keep later users from repeating the same pitfalls, I organized the Docker dependency locking and README parameter documentation, and even committed the changes to my personal fork. However, NOAH's project license explicitly restricts code redistribution and specifically mentions that neither the original nor a modified version may be uploaded to a public GitHub repository. Although GitHub's platform terms have separate provisions for native forks of public repositories, to avoid any conflict in license interpretation and to respect the usage boundaries the authors expressed, I decided not to distribute the modified repository publicly and to keep these changes only in my own internal, non-commercial research environment.
Therefore, this post only shares the deployment process and troubleshooting ideas, without a link to the modified repository. Readers should obtain the code from the original repository and comply with its license; if you need to publish a modified version, distribute it to third parties, or use it commercially, obtain the authors' explicit permission first.
Summary
This NOAH deployment ultimately boils down to four lessons:
NOAH's core idea is valuable, but to turn it into a stable, reproducible server workflow, environment locking, GPU runtime, network configuration, and runtime documentation are all indispensable.