Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ _pull_from_registry: &pull_from_registry
docker pull localhost:5000/fmriprep
docker tag localhost:5000/fmriprep nipreps/fmriprep:latest

_pull_test_image_from_registry: &pull_test_image_from_registry
name: Pull and tag image from local registry
command: |
docker pull localhost:5000/fmriprep-test
docker tag localhost:5000/fmriprep-test nipreps/fmriprep:test

_check_skip_job: &check_skip_job
name: Check commit message and determine if job should be skipped
command: |
Expand Down Expand Up @@ -172,7 +178,7 @@ jobs:
name: Create named builder
command: docker buildx create --use --name=builder --driver=docker-container
- run:
name: Build Docker image
name: Build Docker image (production environment)
no_output_timeout: 60m
command: |
pyenv local 3
Expand All @@ -194,10 +200,20 @@ jobs:
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg VERSION="${CIRCLE_TAG:-$THISVERSION}" .
- run:
name: Build Docker image (test environment)
no_output_timeout: 60m
command: |
docker buildx build --load --builder builder \
--cache-from localhost:5000/fmriprep \
--cache-from nipreps/fmriprep:latest \
-t nipreps/fmriprep:test \
--platform linux/amd64 \
--target test .
- run:
command: docker images
- run:
name: Check Docker image
name: Check Docker image (production)
command: |
# Get version, update files.
THISVERSION=$( hatch version )
Expand All @@ -208,12 +224,19 @@ jobs:
echo "BUILT: \"$BUILT_VERSION\""
set -e
test "$BUILT_VERSION" = "$THISVERSION"
- run:
name: Check Docker image (test)
command: |
docker run --rm nipreps/fmriprep:test fmriprep --version
docker run --rm nipreps/fmriprep:test pytest --version
- run:
name: Docker push to local registry
no_output_timeout: 40m
command: |
docker tag nipreps/fmriprep:latest localhost:5000/fmriprep
docker tag nipreps/fmriprep:test localhost:5000/fmriprep-test
docker push localhost:5000/fmriprep
docker push localhost:5000/fmriprep-test
- run:
name: Docker registry garbage collection
command: |
Expand Down Expand Up @@ -368,14 +391,14 @@ jobs:
- docker/install-docker-credential-helper
- run: *docker_auth
- run: *setup_docker_registry
- run: *pull_from_registry
- run: *pull_test_image_from_registry
- run:
name: Run fMRIPrep tests
no_output_timeout: 2h
command: |
docker run -ti --rm=false \
-e TEST_READONLY_FILESYSTEM=1 -v $HOME:/home/readonly:ro \
--entrypoint="pytest" nipreps/fmriprep:latest \
--entrypoint="pytest" nipreps/fmriprep:test \
--pyargs fmriprep -svx --doctest-modules

- run:
Expand Down
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ fmriprep.egg-info/**/*
fmriprep.egg-info
.eggs/**/*
.eggs

.pixi
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.git_archival.txt export-subst
# SCM syntax highlighting & preventing 3-way merges
pixi.lock merge=binary linguist-language=YAML linguist-generated=true
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,6 @@ local_settings.py
*.swp

_version.py
# pixi environments
.pixi/*
!.pixi/config.toml
228 changes: 99 additions & 129 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,66 @@
# SOFTWARE.

# Ubuntu 22.04 LTS - Jammy
ARG BASE_IMAGE=ubuntu:jammy-20240125
ARG BASE_IMAGE=ubuntu:jammy-20250730

#
# Build wheel
# Build pixi environment
# The Pixi environment includes:
# - Python
# - Scientific Python stack (via conda-forge)
# - General Python dependencies (via PyPI)
# - NodeJS
# - bids-validator
# - svgo
# - FSL (via fslconda)
# - ants (via conda-forge)
# - connectome-workbench (via conda-forge)
# - ...
#
FROM ghcr.io/astral-sh/uv:python3.12-alpine AS src
RUN apk add git
COPY . /src
RUN uvx --from build pyproject-build --installer uv -w /src
FROM ghcr.io/prefix-dev/pixi:0.53.0 AS build
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
git && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Run post-link scripts during install, but use global to keep out of source tree
RUN pixi config set --global run-post-link-scripts insecure

# Install dependencies before the package itself to leverage caching
RUN mkdir /app
COPY pixi.lock pyproject.toml /app
WORKDIR /app
RUN --mount=type=cache,target=/root/.cache/rattler pixi install -e fmriprep -e test --frozen --skip fmriprep
RUN --mount=type=cache,target=/root/.npm pixi run --as-is -e fmriprep npm install -g svgo@^3.2.0 [email protected]
# Note that PATH gets hard-coded. Remove it and re-apply in final image
RUN pixi shell-hook -e fmriprep --as-is | grep -v PATH > /shell-hook.sh
RUN pixi shell-hook -e test --as-is | grep -v PATH > /test-shell-hook.sh

# Finally, install the package
COPY . /app
RUN --mount=type=cache,target=/root/.cache/rattler pixi install -e fmriprep -e test --frozen

#
# Pre-fetch templates
#
FROM ghcr.io/astral-sh/uv:python3.12-alpine AS templates
ENV TEMPLATEFLOW_HOME="/templateflow"
RUN uv pip install --system templateflow
COPY scripts/fetch_templates.py fetch_templates.py
RUN python fetch_templates.py

#
# Download stages
#

# Utilities for downloading packages
FROM ${BASE_IMAGE} AS downloader
ENV DEBIAN_FRONTEND="noninteractive" \
LANG="en_US.UTF-8" \
LC_ALL="en_US.UTF-8"

# Bump the date to current to refresh curl/certificates/etc
RUN echo "2023.07.20"
RUN echo "2025.08.20"
RUN apt-get update && \
apt-get install -y --no-install-recommends \
binutils \
Expand All @@ -56,59 +98,15 @@ COPY docker/files/freesurfer7.3.2-exclude.txt /usr/local/etc/freesurfer7.3.2-exc
RUN curl -sSL https://surfer.nmr.mgh.harvard.edu/pub/dist/freesurfer/7.3.2/freesurfer-linux-ubuntu22_amd64-7.3.2.tar.gz \
| tar zxv --no-same-owner -C /opt --exclude-from=/usr/local/etc/freesurfer7.3.2-exclude.txt

# AFNI
FROM downloader AS afni
# Bump the date to current to update AFNI
RUN echo "2023.07.20"
RUN mkdir -p /opt/afni-latest \
&& curl -fsSL --retry 5 https://afni.nimh.nih.gov/pub/dist/tgz/linux_openmp_64.tgz \
| tar -xz -C /opt/afni-latest --strip-components 1 \
--exclude "linux_openmp_64/*.gz" \
--exclude "linux_openmp_64/funstuff" \
--exclude "linux_openmp_64/shiny" \
--exclude "linux_openmp_64/afnipy" \
--exclude "linux_openmp_64/lib/RetroTS" \
--exclude "linux_openmp_64/lib_RetroTS" \
--exclude "linux_openmp_64/meica.libs" \
# Keep only what we use
&& find /opt/afni-latest -type f -not \( \
-name "3dTshift" -or \
-name "3dUnifize" -or \
-name "3dAutomask" -or \
-name "3dvolreg" \) -delete

# Micromamba
FROM downloader AS micromamba

# Install a C compiler to build extensions when needed.
# traits<6.4 wheels are not available for Python 3.11+, but build easily.
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

WORKDIR /
# Bump the date to current to force update micromamba
RUN echo "2024.02.06"
RUN curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba

ENV MAMBA_ROOT_PREFIX="/opt/conda"
COPY env.yml /tmp/env.yml
COPY requirements.txt /tmp/requirements.txt
WORKDIR /tmp
RUN micromamba create -y -f /tmp/env.yml && \
micromamba clean -y -a

# UV_USE_IO_URING for apparent race-condition (https://github.com/nodejs/node/issues/48444)
# Check if this is still necessary when updating the base image.
ENV PATH="/opt/conda/envs/fmriprep/bin:$PATH" \
UV_USE_IO_URING=0
RUN npm install -g svgo@^3.2.0 [email protected] && \
rm -r ~/.npm
# MSM HOCR (Nov 19, 2019 release)
FROM downloader AS msm
RUN curl -L -H "Accept: application/octet-stream" https://api.github.com/repos/ecr05/MSM_HOCR/releases/assets/16253707 -o /usr/local/bin/msm \
&& chmod +x /usr/local/bin/msm

#
# Main stage
#
FROM ${BASE_IMAGE} AS fmriprep
FROM ${BASE_IMAGE} AS base

# Configure apt
ENV DEBIAN_FRONTEND="noninteractive" \
Expand All @@ -121,50 +119,34 @@ RUN apt-get update && \
bc \
ca-certificates \
curl \
git \
gnupg \
libgomp1 \
libopenblas0-openmp \
lsb-release \
netbase \
tcsh \
xvfb && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

# Configure PPAs for libpng12 and libxp6
RUN GNUPGHOME=/tmp gpg --keyserver hkps://keyserver.ubuntu.com --no-default-keyring --keyring /usr/share/keyrings/linuxuprising.gpg --recv 0xEA8CACC073C3DB2A \
&& GNUPGHOME=/tmp gpg --keyserver hkps://keyserver.ubuntu.com --no-default-keyring --keyring /usr/share/keyrings/zeehio.gpg --recv 0xA1301338A3A48C4A \
&& echo "deb [signed-by=/usr/share/keyrings/linuxuprising.gpg] https://ppa.launchpadcontent.net/linuxuprising/libpng12/ubuntu jammy main" > /etc/apt/sources.list.d/linuxuprising.list \
&& echo "deb [signed-by=/usr/share/keyrings/zeehio.gpg] https://ppa.launchpadcontent.net/zeehio/libxp/ubuntu jammy main" > /etc/apt/sources.list.d/zeehio.list

# Dependencies for AFNI; requires a discontinued multiarch-support package from bionic (18.04)
RUN apt-get update -qq \
&& apt-get install -y -q --no-install-recommends \
ed \
gsl-bin \
libglib2.0-0 \
libglu1-mesa-dev \
libglw1-mesa \
libgomp1 \
libjpeg62 \
libpng12-0 \
libxm4 \
libxp6 \
netpbm \
tcsh \
xfonts-base \
xvfb \
&& curl -sSL --retry 5 -o /tmp/multiarch.deb http://archive.ubuntu.com/ubuntu/pool/main/g/glibc/multiarch-support_2.27-3ubuntu1.5_amd64.deb \
&& dpkg -i /tmp/multiarch.deb \
&& rm /tmp/multiarch.deb \
&& apt-get install -f \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& gsl2_path="$(find / -name 'libgsl.so.19' || printf '')" \
&& if [ -n "$gsl2_path" ]; then \
ln -sfv "$gsl2_path" "$(dirname $gsl2_path)/libgsl.so.0"; \
fi \
&& ldconfig

# Install files from stages
COPY --from=freesurfer /opt/freesurfer /opt/freesurfer
COPY --from=afni /opt/afni-latest /opt/afni-latest

# Install downloaded files from stages
COPY --link --from=freesurfer /opt/freesurfer /opt/freesurfer
COPY --link --from=msm /usr/local/bin/msm /usr/local/bin/msm

# Install AFNI from Docker container
# Find libraries with `ldd $BINARIES | grep afni`
COPY --link --from=afni/afni_make_build:AFNI_25.2.09 \
/opt/afni/install/libf2c.so \
/opt/afni/install/libmri.so \
/usr/local/lib/
COPY --link --from=afni/afni_make_build:AFNI_25.2.09 \
/opt/afni/install/3dAutomask \
/opt/afni/install/3dTshift \
/opt/afni/install/3dUnifize \
/opt/afni/install/3dvolreg \
/usr/local/bin/

# Changing library paths requires a re-ldconfig
RUN ldconfig

# Simulate SetUpFreeSurfer.sh
ENV OS="Linux" \
Expand All @@ -184,38 +166,19 @@ ENV PERL5LIB="$MINC_LIB_DIR/perl5/5.8.5" \
PATH="$FREESURFER_HOME/bin:$FREESURFER_HOME/tktools:$MINC_BIN_DIR:$PATH"

# AFNI config
ENV PATH="/opt/afni-latest:$PATH" \
AFNI_IMSAVE_WARNINGS="NO" \
AFNI_PLUGINPATH="/opt/afni-latest"
ENV AFNI_IMSAVE_WARNINGS="NO"

# Create a shared $HOME directory
RUN useradd -m -s /bin/bash -G users fmriprep
WORKDIR /home/fmriprep
ENV HOME="/home/fmriprep" \
LD_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH"
ENV HOME="/home/fmriprep"

COPY --from=micromamba /bin/micromamba /bin/micromamba
COPY --from=micromamba /opt/conda/envs/fmriprep /opt/conda/envs/fmriprep

ENV MAMBA_ROOT_PREFIX="/opt/conda"
RUN micromamba shell init -s bash && \
echo "micromamba activate fmriprep" >> $HOME/.bashrc
ENV PATH="/opt/conda/envs/fmriprep/bin:$PATH" \
CPATH="/opt/conda/envs/fmriprep/include:$CPATH" \
LD_LIBRARY_PATH="/opt/conda/envs/fmriprep/lib:$LD_LIBRARY_PATH"

# Precaching atlases
COPY scripts/fetch_templates.py fetch_templates.py
RUN python fetch_templates.py && \
rm fetch_templates.py && \
find $HOME/.cache/templateflow -type d -exec chmod go=u {} + && \
find $HOME/.cache/templateflow -type f -exec chmod go=u {} +
COPY --link --from=templates /templateflow /home/fmriprep/.cache/templateflow

# FSL environment
ENV LANG="C.UTF-8" \
LC_ALL="C.UTF-8" \
PYTHONNOUSERSITE=1 \
FSLDIR="/opt/conda/envs/fmriprep" \
FSLOUTPUTTYPE="NIFTI_GZ" \
FSLMULTIFILEQUIT="TRUE" \
FSLLOCKDIR="" \
Expand All @@ -228,24 +191,31 @@ ENV LANG="C.UTF-8" \
ENV MKL_NUM_THREADS=1 \
OMP_NUM_THREADS=1

# MSM HOCR (Nov 19, 2019 release)
RUN curl -L -H "Accept: application/octet-stream" https://api.github.com/repos/ecr05/MSM_HOCR/releases/assets/16253707 -o /usr/local/bin/msm \
&& chmod +x /usr/local/bin/msm
WORKDIR /tmp

FROM base AS test

COPY --link --from=build /app/.pixi/envs/test /app/.pixi/envs/test
COPY --link --from=build /test-shell-hook.sh /shell-hook.sh
RUN cat /shell-hook.sh >> $HOME/.bashrc
ENV PATH="/app/.pixi/envs/test/bin:$PATH"

# Installing FMRIPREP
COPY --from=src /src/dist/*.whl .
RUN pip install --no-cache-dir $( ls *.whl )[container,test]
ENV FSLDIR="/app/.pixi/envs/test"

RUN find $HOME -type d -exec chmod go=u {} + && \
find $HOME -type f -exec chmod go=u {} + && \
rm -rf $HOME/.npm $HOME/.conda $HOME/.empty
FROM base AS fmriprep

# Keep synced with wrapper's PKG_PATH
COPY --link --from=build /app/.pixi/envs/fmriprep /app/.pixi/envs/fmriprep
COPY --link --from=build /shell-hook.sh /shell-hook.sh
RUN cat /shell-hook.sh >> $HOME/.bashrc
ENV PATH="/app/.pixi/envs/fmriprep/bin:$PATH"

ENV FSLDIR="/app/.pixi/envs/fmriprep"

# For detecting the container
ENV IS_DOCKER_8395080871=1

RUN ldconfig
WORKDIR /tmp
ENTRYPOINT ["/opt/conda/envs/fmriprep/bin/fmriprep"]
ENTRYPOINT ["/app/.pixi/envs/fmriprep/bin/fmriprep"]

ARG BUILD_DATE
ARG VCS_REF
Expand Down
Loading
Loading