Land/ice container
Builds the daily land/ice mask from OSI-SAF sea-ice concentration, and the ice-edge SST points that go with it.
This MATLAB application automates landmask and ice operations for MUR SST input data. It processes data for two grid resolutions (p01 at 0.01° and p011 at 0.011°) using OSI SAF sea ice datasets and has been containerized for easy deployment.
Overview
The application:
- Processes land ice operations for a specified year and day-of-year
- Handles both P01 and P011 grid resolutions automatically in each run
- Uses OSI SAF FTP endpoints for sea ice data access
- Compiled as a standalone MATLAB executable for containerized deployment
- No MATLAB license required to run the compiled version
- Takes every static input file as an explicit named flag — no directory is bind-mounted whole and scanned; the calling orchestrator (
run_mur_pipeline.pylocally,run_mur_maap.pyon MAAP) resolves and passes each of the six static files individually - Each
--*-fileflag value may be either a local filesystem path (localdocker run, bind-mounted) or ans3://href (MAAP, or any environment with S3 access) — the container fetchess3://values to local scratch space itself before MATLAB runs, so the same image works unchanged in either environment (see Container Features below) - Optimized with shared memory configuration for MATLAB Runtime performance
Quick Start - Containerized Deployment (Recommended)
Build and Run
⚠️ Important: The build process requires a valid MATLAB license server connection during compilation. If the license server is unreachable, the build may hang at the compilation step. Copy network.lic.example to mur/network.lic and point it at your license server before the first build.
# Build with build_module.sh from the mur/ directory
cd mur
./build_module.sh landice
# Run — every static input is an explicit flag, bind-mounted individually
docker run --rm --shm-size=512M \
-e OSISAF_FTP_ARCHIVE="https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc" \
-e OSISAF_FTP_PROD="https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc" \
-v /path/to/static-resources/grids/maskGLOBp01deg.gds:/input/landmask-p01.gds:ro \
-v /path/to/static-resources/mat/p01/saf2north.mat:/input/gridindex-north-p01.mat:ro \
-v /path/to/static-resources/mat/p01/saf2south.mat:/input/gridindex-south-p01.mat:ro \
-v /path/to/static-resources/grids/maskGlob1km.gds:/input/landmask-p011.gds:ro \
-v /path/to/static-resources/mat/p011/saf2north.mat:/input/gridindex-north-p011.mat:ro \
-v /path/to/static-resources/mat/p011/saf2south.mat:/input/gridindex-south-p011.mat:ro \
-v /path/to/output/p011:/output/p011 \
-v /path/to/output/p01:/output/p01 \
mur-landice:latest \
--year 2024 --doy 100 \
--landmask-p01-file /input/landmask-p01.gds \
--gridindex-north-p01-file /input/gridindex-north-p01.mat \
--gridindex-south-p01-file /input/gridindex-south-p01.mat \
--landmask-p011-file /input/landmask-p011.gds \
--gridindex-north-p011-file /input/gridindex-north-p011.mat \
--gridindex-south-p011-file /input/gridindex-south-p011.mat
See Static input data for where the six static files (grids/maskGLOBp01deg.gds, mat/p01/saf2north.mat, etc.) come from and their full-tree layout.
Manual Build (Advanced)
build_module.sh is the supported path. Build by hand only for a custom tag, external CI, or Dockerfile debugging — the build context must be the parent mur directory so the Dockerfile can reach common/, and the mur-matlab-base:r2024b image must already exist (./build_matlab_base.sh from mur/):
cd mur/landice
docker build --platform linux/amd64 -f Dockerfile -t mur-landice:latest ..
# Or with Apple's container tools on macOS:
# container build --arch amd64 -f Dockerfile -t mur-landice:latest ..
See Manual Builds (Advanced) and Building the image for the in-container mcc workflow.
Container Features
- Explicit named inputs: every static file (landmask + grid-index files, both resolutions) is its own
--flag; there is no directory mount to scan - Named args only:
--year/--doyplus the six file flags — no positional-argument form is accepted - Local-path or S3-href inputs, uniformly:
entrypoint.shsources the sharedcommon/bin/localize.shhelper and resolves each of the six file flags before MATLAB runs — a value starting withs3://is fetched to local scratch space (viaaws s3 cp) and the local path substituted; any other value is assumed to already be a local path (e.g. a bind mount) and passed through unchanged. AWS credentials come from the standard AWS credential chain — environment variables (AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY/AWS_SESSION_TOKEN) for local dev, an IAM role automatically when running on AWS/MAAP compute — no extra flags or config needed either way. - Automatic processing: Processes both P01 and P011 grids in a single run
- Optimized performance: Requires
--shm-size=512Mfor MATLAB Runtime (mandatory) - No license required: Self-contained with MATLAB Runtime R2024b
- Memory optimization: Supports configurable memory limits and Java heap settings
Requirements
For Containerized Deployment
- Docker
- The six static input files (see Static input data)
- Output directories (one per resolution:
p011,p01)
For Development
- MATLAB R2024b with MATLAB Compiler
- Access to MATLAB license server
Usage Examples
Basic Usage
# Process day 150 of 2024
docker run --rm --shm-size=512M \
-v /data/static-resources/grids/maskGLOBp01deg.gds:/input/landmask-p01.gds:ro \
-v /data/static-resources/mat/p01/saf2north.mat:/input/gridindex-north-p01.mat:ro \
-v /data/static-resources/mat/p01/saf2south.mat:/input/gridindex-south-p01.mat:ro \
-v /data/static-resources/grids/maskGlob1km.gds:/input/landmask-p011.gds:ro \
-v /data/static-resources/mat/p011/saf2north.mat:/input/gridindex-north-p011.mat:ro \
-v /data/static-resources/mat/p011/saf2south.mat:/input/gridindex-south-p011.mat:ro \
-v /data/output/p011:/output/p011 \
-v /data/output/p01:/output/p01 \
mur-landice:latest \
--year 2024 --doy 150 \
--landmask-p01-file /input/landmask-p01.gds \
--gridindex-north-p01-file /input/gridindex-north-p01.mat \
--gridindex-south-p01-file /input/gridindex-south-p01.mat \
--landmask-p011-file /input/landmask-p011.gds \
--gridindex-north-p011-file /input/gridindex-north-p011.mat \
--gridindex-south-p011-file /input/gridindex-south-p011.mat
With Custom OSI SAF Endpoints
docker run --rm --shm-size=512M \
-e OSISAF_FTP_REPROCESSED="ftp://your-server/reprocessed" \
-e OSISAF_FTP_ARCHIVE="ftp://your-server/archive" \
-e OSISAF_FTP_PROD="ftp://your-server/prod" \
-v /data/static-resources/grids/maskGLOBp01deg.gds:/input/landmask-p01.gds:ro \
-v /data/static-resources/mat/p01/saf2north.mat:/input/gridindex-north-p01.mat:ro \
-v /data/static-resources/mat/p01/saf2south.mat:/input/gridindex-south-p01.mat:ro \
-v /data/static-resources/grids/maskGlob1km.gds:/input/landmask-p011.gds:ro \
-v /data/static-resources/mat/p011/saf2north.mat:/input/gridindex-north-p011.mat:ro \
-v /data/static-resources/mat/p011/saf2south.mat:/input/gridindex-south-p011.mat:ro \
-v /data/output/p011:/output/p011 \
-v /data/output/p01:/output/p01 \
mur-landice:latest \
--year 2024 --doy 150 \
--landmask-p01-file /input/landmask-p01.gds \
--gridindex-north-p01-file /input/gridindex-north-p01.mat \
--gridindex-south-p01-file /input/gridindex-south-p01.mat \
--landmask-p011-file /input/landmask-p011.gds \
--gridindex-north-p011-file /input/gridindex-north-p011.mat \
--gridindex-south-p011-file /input/gridindex-south-p011.mat
With Performance Optimization
docker run --rm \
--memory=4g \
--memory-reservation=2g \
--memory-swap=6g \
--shm-size=512M \
--cpus="2.0" \
-e MCR_CACHE_ROOT=/tmp/mcr_cache \
-e MCR_CACHE_SIZE=1024M \
-e _JAVA_OPTIONS="-Xmx2048m -Xms512m -XX:+UseG1GC" \
-e OSISAF_FTP_ARCHIVE="https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc" \
-e OSISAF_FTP_PROD="https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc" \
-v /data/static-resources/grids/maskGLOBp01deg.gds:/input/landmask-p01.gds:ro \
-v /data/static-resources/mat/p01/saf2north.mat:/input/gridindex-north-p01.mat:ro \
-v /data/static-resources/mat/p01/saf2south.mat:/input/gridindex-south-p01.mat:ro \
-v /data/static-resources/grids/maskGlob1km.gds:/input/landmask-p011.gds:ro \
-v /data/static-resources/mat/p011/saf2north.mat:/input/gridindex-north-p011.mat:ro \
-v /data/static-resources/mat/p011/saf2south.mat:/input/gridindex-south-p011.mat:ro \
-v /data/output/p011:/output/p011 \
-v /data/output/p01:/output/p01 \
mur-landice:latest \
--year 2024 --doy 150 \
--landmask-p01-file /input/landmask-p01.gds \
--gridindex-north-p01-file /input/gridindex-north-p01.mat \
--gridindex-south-p01-file /input/gridindex-south-p01.mat \
--landmask-p011-file /input/landmask-p011.gds \
--gridindex-north-p011-file /input/gridindex-north-p011.mat \
--gridindex-south-p011-file /input/gridindex-south-p011.mat
Batch Processing
For processing multiple days, create a simple bash script:
#!/bin/bash
YEAR=2024
START_DOY=1
END_DOY=365
STATIC=/data/static-resources
for DOY in $(seq $START_DOY $END_DOY); do
echo "Processing Year: $YEAR, DOY: $DOY"
docker run --rm --shm-size=512M \
-e OSISAF_FTP_ARCHIVE="https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc" \
-e OSISAF_FTP_PROD="https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc" \
-v "$STATIC/grids/maskGLOBp01deg.gds:/input/landmask-p01.gds:ro" \
-v "$STATIC/mat/p01/saf2north.mat:/input/gridindex-north-p01.mat:ro" \
-v "$STATIC/mat/p01/saf2south.mat:/input/gridindex-south-p01.mat:ro" \
-v "$STATIC/grids/maskGlob1km.gds:/input/landmask-p011.gds:ro" \
-v "$STATIC/mat/p011/saf2north.mat:/input/gridindex-north-p011.mat:ro" \
-v "$STATIC/mat/p011/saf2south.mat:/input/gridindex-south-p011.mat:ro" \
-v /data/output/p011:/output/p011 \
-v /data/output/p01:/output/p01 \
mur-landice:latest \
--year "$YEAR" --doy "$DOY" \
--landmask-p01-file /input/landmask-p01.gds \
--gridindex-north-p01-file /input/gridindex-north-p01.mat \
--gridindex-south-p01-file /input/gridindex-south-p01.mat \
--landmask-p011-file /input/landmask-p011.gds \
--gridindex-north-p011-file /input/gridindex-north-p011.mat \
--gridindex-south-p011-file /input/gridindex-south-p011.mat
if [ $? -ne 0 ]; then
echo "Error processing Year: $YEAR, DOY: $DOY"
fi
done
In practice, run_mur_pipeline.py already does this per-day looping and flag construction for you — see the main the project README.
Environment Variables
The application uses these environment variables to locate OSI-SAF ice concentration data. OSI-SAF's anonymous FTP host is dead — connections to ftp://osisaf.met.no time out — so the defaults are HTTPS THREDDS endpoints. The names keep their _FTP_ spelling because they are part of the container's published interface; readosisafice.m dispatches on the URL scheme, so an ftp:// value still works if one is supplied.
OSISAF_FTP_ARCHIVE: recent days (default:https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc)OSISAF_FTP_PROD: current day (default: same as archive)OSISAF_FTP_REPROCESSED: pre-2009 dates — no default. Thepolstere-100"reproc" product this once pointed at has no verified HTTPS equivalent on thredds.met.no, so historical reprocessing must supply it explicitly rather than inherit a dead URL.
Note on the amsr2_conc path: despite the name, that tree serves AMSR3 files from 2026-08-31 onward. readosisafice.m selects the amsr2/amsr3 filename token by date; the cutover is overridable with OSISAF_AMSR3_START=YYYYMMDD.
Directory Structure
landice/
├── src/
│ ├── landice_wrapper.m # Compiled entry point — accepts the six explicit static file paths
│ ├── makeicefiles.m # Ice file generation (core logic)
│ ├── readosisafice.m # OSI SAF data reader and downloader
│ └── setup_environment.m # Environment setup
├── bin/
│ └── entrypoint.sh # Named-args-only container entrypoint; localizes s3:// inputs before invoking MATLAB
├── tests/
│ ├── test_entrypoint.sh # Entrypoint argument-parsing + localization unit tests
│ ├── in/, out/ # Test fixtures
│ └── compare_outputs.py, validate_containerized.sh, test_execute_landice.py
└── Dockerfile # Multi-stage build (MATLAB compiler → runtime-only image, includes AWS CLI)
../common/
├── bin/
│ └── localize.sh # Shared s3://-or-local-path resolver, sourced by entrypoint.sh (also used by other containers as they're converted)
└── *.m # Shared MATLAB utilities (compiled into the MATLAB build stage)
Documentation
- Building the image - Complete build instructions for development
- Deployment - Production deployment guide with Docker Compose and Kubernetes examples
- Static input data - Where the six static input files live and how they're laid out
Production Deployment
Docker Compose
Create docker-compose.yml:
version: '3.8'
services:
landice:
image: mur-landice:latest
environment:
- OSISAF_FTP_REPROCESSED=${OSISAF_FTP_REPROCESSED}
- OSISAF_FTP_ARCHIVE=${OSISAF_FTP_ARCHIVE}
- OSISAF_FTP_PROD=${OSISAF_FTP_PROD}
volumes:
- ${STATIC_RESOURCES_DIR}/grids/maskGLOBp01deg.gds:/input/landmask-p01.gds:ro
- ${STATIC_RESOURCES_DIR}/mat/p01/saf2north.mat:/input/gridindex-north-p01.mat:ro
- ${STATIC_RESOURCES_DIR}/mat/p01/saf2south.mat:/input/gridindex-south-p01.mat:ro
- ${STATIC_RESOURCES_DIR}/grids/maskGlob1km.gds:/input/landmask-p011.gds:ro
- ${STATIC_RESOURCES_DIR}/mat/p011/saf2north.mat:/input/gridindex-north-p011.mat:ro
- ${STATIC_RESOURCES_DIR}/mat/p011/saf2south.mat:/input/gridindex-south-p011.mat:ro
- ${OUTPUT_DIR_P011}:/output/p011
- ${OUTPUT_DIR_P01}:/output/p01
command:
- "--year"
- "${YEAR}"
- "--doy"
- "${DOY}"
- "--landmask-p01-file"
- "/input/landmask-p01.gds"
- "--gridindex-north-p01-file"
- "/input/gridindex-north-p01.mat"
- "--gridindex-south-p01-file"
- "/input/gridindex-south-p01.mat"
- "--landmask-p011-file"
- "/input/landmask-p011.gds"
- "--gridindex-north-p011-file"
- "/input/gridindex-north-p011.mat"
- "--gridindex-south-p011-file"
- "/input/gridindex-south-p011.mat"
shm_size: '512m'
Kubernetes
Deploy as a Job:
apiVersion: batch/v1
kind: Job
metadata:
name: landice-processor
spec:
template:
spec:
containers:
- name: landice
image: mur-landice:latest
args:
- "--year"
- "2024"
- "--doy"
- "100"
- "--landmask-p01-file"
- "/input/landmask-p01.gds"
- "--gridindex-north-p01-file"
- "/input/gridindex-north-p01.mat"
- "--gridindex-south-p01-file"
- "/input/gridindex-south-p01.mat"
- "--landmask-p011-file"
- "/input/landmask-p011.gds"
- "--gridindex-north-p011-file"
- "/input/gridindex-north-p011.mat"
- "--gridindex-south-p011-file"
- "/input/gridindex-south-p011.mat"
env:
- name: OSISAF_FTP_ARCHIVE
value: "https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc"
- name: OSISAF_FTP_PROD
value: "https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc"
volumeMounts:
- name: static-resources
mountPath: /input
readOnly: true
- name: output-p011
mountPath: /output/p011
- name: output-p01
mountPath: /output/p01
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "4Gi"
cpu: "2"
restartPolicy: OnFailure
(The example mounts a directory at /input for brevity here — in practice each --*-file flag should point at an individually-mounted file, matching the docker run examples above, so nothing inside the container ever scans a directory to find its inputs.)
Troubleshooting
Build Issues
Build hangs during compilation:
- Check MATLAB license server connectivity
- Ensure valid MATLAB Compiler license is available
- Verify network access to license servers (may require VPN)
Runtime Issues
Memory errors:
# Increase memory allocation (append to any of the examples above)
docker run --rm \
--shm-size=512M \
--memory="8g" \
--memory-swap="16g" \
... \
mur-landice:latest \
--year 2024 --doy 100 \
--landmask-p01-file /input/landmask-p01.gds \
--gridindex-north-p01-file /input/gridindex-north-p01.mat \
--gridindex-south-p01-file /input/gridindex-south-p01.mat \
--landmask-p011-file /input/landmask-p011.gds \
--gridindex-north-p011-file /input/gridindex-north-p011.mat \
--gridindex-south-p011-file /input/gridindex-south-p011.mat
Permission issues:
# Run with user permissions
docker run --rm \
--shm-size=512M \
--user $(id -u):$(id -g) \
... \
mur-landice:latest \
--year 2024 --doy 100 \
--landmask-p01-file /input/landmask-p01.gds \
--gridindex-north-p01-file /input/gridindex-north-p01.mat \
--gridindex-south-p01-file /input/gridindex-south-p01.mat \
--landmask-p011-file /input/landmask-p011.gds \
--gridindex-north-p011-file /input/gridindex-north-p011.mat \
--gridindex-south-p011-file /input/gridindex-south-p011.mat
Missing/unknown-flag errors: all nine flags (--year, --doy, and the six --*-file flags) are required — positional arguments are no longer accepted. Run docker run --rm mur-landice:latest with no arguments to see the usage message.
Resolution Notes
The landice container generates two grid resolutions per run:
| Resolution | Grid | Output directory | Filename pattern | Used by |
|---|---|---|---|---|
| p01 (0.01°) | maskGLOBp01deg.gds | land/p01/YYYY/ | landiceP01_YYYY_DDD.gds.gz | MUR v4 (current) |
| p011 (0.011°/~1km) | maskGlob1km.gds | land/p011/YYYY/ | landice_YYYY_DDD.gds.gz | MUR v3 (legacy, not used) |
Only p01 output is used by the current MUR v4 pipeline. The p011 resolution is a legacy artifact from MUR v3 (0.011° product). The MRVA container mounts only the land/p01/ subdirectory and expects landiceP01_ prefixed filenames. The p011 output is generated but not consumed by any current processing stage.
In the original production system, both resolutions were written to the same flat directory (/nas2/landice/YYYY/), so the P01 filename prefix was necessary to distinguish them. In the containerized setup, they are separated into land/p01/ and land/p011/ subdirectories, making the prefix redundant — but it is retained for compatibility with the MRVA MATLAB code that expects it.
General Notes
- Execution time is logged for monitoring purposes
- Container uses MATLAB Runtime R2024b - no MATLAB license required for execution
- Shared memory (
--shm-size=512M) is mandatory for MATLAB Runtime - Every static input is passed as its own bind-mounted file and named flag — nothing is discovered by scanning a directory
- Each of the two output directories (
p011,p01) will contain the processed land ice files for that resolution
Building the image
Quick Start (Recommended)
Build with build_module.sh from the mur/ directory. It builds the mur-matlab-base:r2024b image if missing, checks network.lic, sets --platform linux/amd64, uses the parent mur/ directory as build context (so the Dockerfile can reach common/), and tags the image mur-landice:latest:
cd mur
./build_module.sh landice
Run it (see the usage examples above for the full named-flag invocation):
docker run --rm --shm-size=512M \
-v /path/to/static-resources:/input:ro \
-v /path/to/output:/output \
mur-landice:latest \
--year 2024 --doy 100 ...
For a raw docker build — custom tags, external CI, Dockerfile debugging — see Manual Builds (Advanced):
cd mur/landice
docker build --platform linux/amd64 -f Dockerfile -t mur-landice:latest ..
Manual Compilation Process (Advanced)
The rest of this document covers compiling the MATLAB application by hand inside a development container — useful when debugging mcc dependency problems or toolbox requirements, not part of the normal build path. The single landice/Dockerfile does all of this automatically; the Dockerfile.matlab / Dockerfile.runtime split described below is historical, so adapt the commands to the current Dockerfile's builder stage.
Prerequisites
- Docker installed on your system
- Access to MATLAB license server
- Network connectivity to download MATLAB toolboxes
Find the required toolboxes with
[files, products] = matlab.codetools.requiredFilesAndProducts('myScript.m');
% Display required toolboxes
for i = 1:length(products)
fprintf('Required: %s\n', products(i).Name);
end
Building the MATLAB Development Container
1. Build the Docker Image
Note: Dockerfile.matlab no longer exists. The equivalent today is the builder stage of landice/Dockerfile, or the shared base image mur-matlab-base:r2024b built by ./build_matlab_base.sh. Either gives you MATLAB R2024b + Compiler with common/ available:
# Build the shared MATLAB base image (from mur/)
cd mur
./build_matlab_base.sh
# Or stop the landice build at its builder stage
cd mur/landice
docker build --platform linux/amd64 --target builder -t matlab-landice:r2024b -f Dockerfile ..
Both build with the parent mur directory as context, which is what lets the Dockerfile reach landice/ and common/. Substitute whichever image you built for matlab-landice:r2024b in the commands below.
2. Run Interactive Terminal Session
# Run container with volume mounting
# Builds to /tmp inside container to avoid permission issues
docker run -it --rm \
-v $(pwd):/app/landice \
matlab-landice:r2024b
Note:
- Container runs as the
matlabuser (not root) to comply with license server restrictions - Build process uses
/tmpto avoid volume mount permission issues
3. Build the LandIce Application Inside Container
Once inside the container terminal:
# Navigate to the landice directory
cd /app/landice
# Method 1: Build to temp location and copy back (recommended)
# This avoids volume mount permission issues
# -C flag creates separate CTF file for faster runtime extraction
mkdir -p /tmp/landice_build
mcc -m src/landice_wrapper.m \
-a src/makeicefiles.m \
-a src/readosisafice.m \
-a src/setup_environment.m \
-a src/julian.m \
-o LandIce \
-d /tmp/landice_build \
-C \
-R -nodisplay \
-R -nosplash
# Copy the compiled files back to the mounted volume
cp -R /tmp/landice_build/LandIce ./build
Alternative methods:
# Method 2: Auto-detect dependencies (build to temp and copy)
mkdir -p /tmp/landice_build
mcc -m -v -d /tmp/landice_build -o LandIce src/landice_wrapper.m
mkdir -p ./build && cp /tmp/landice_build/* ./build/
# Method 3: Interactive MATLAB (if needed for debugging)
matlab -nodisplay -nosplash
# In MATLAB:
# >> mcc -m src/landice_wrapper.m -a src/makeicefiles.m -a src/readosisafice.m -a src/setup_environment.m -a src/julian.m -o LandIce -d /tmp/landice_build
# >> exit
# Then copy files: cp /tmp/landice_build/* ./build/
Notes:
- Building to
/tmp/landice_buildavoids volume mount permission issues completely - The
-mflag creates a standalone executable binary (not an installer) - The
-Cflag creates a separate CTF archive for faster runtime extraction - The
-nflag automatically treats numeric command line arguments as MATLAB doubles (solves string conversion issues) - The resulting executable only requires MATLAB Runtime R2024b, not full MATLAB
- The
-vflag provides verbose output for troubleshooting - MATLAB R2015b+ automatically finds dependencies, so
-afor each file may not be necessary - Files are copied back to
./build/which is accessible from the host system
Output Files: The compilation will produce:
LandIce- The standalone executable binary (no installer)LandIce.ctf- Separate CTF (Component Technology File) archive for faster runtime extractionrun_LandIce.sh- Shell script wrapper that sets up environment variablesreadme.txt- Runtime requirements and deployment instructionsrequiredMCRProducts.txt- List of required MATLAB Runtime components
Running the Compiled Executable
Direct Execution (with MATLAB Runtime installed)
# Using the wrapper script (recommended - handles environment setup)
# MCR path may vary by installation - check your system
./build/run_LandIce.sh /usr/local/MATLAB/MATLAB_Runtime/R2024b input_dir /path/to/input output_dir /path/to/output year 2024 doy 100
# Alternative common MCR paths:
./build/run_LandIce.sh /opt/mcr/R2024b input_dir /path/to/input output_dir /path/to/output year 2024 doy 100
# Or directly (requires LD_LIBRARY_PATH to be set)
export MCR_ROOT=/usr/local/MATLAB/MATLAB_Runtime/R2024b
export LD_LIBRARY_PATH=$MCR_ROOT/runtime/glnxa64:$MCR_ROOT/bin/glnxa64:$MCR_ROOT/sys/os/glnxa64:$LD_LIBRARY_PATH
./build/LandIce input_dir /path/to/input output_dir /path/to/output year 2024 doy 100
Note: When running the compiled executable directly (not in container), you need to specify input and output paths. The container entrypoint automatically handles this by passing input_dir /input output_dir /output to the application.
Important:
- The executable is a binary file, NOT an installer
- It only requires MATLAB Runtime R2024b to be installed
- No MATLAB license is needed to run the compiled executable
- The runtime can be freely distributed
- MCR installation path varies by system - check your actual installation location
Building Runtime-Only Container
1. Create Runtime Dockerfile
Historical: the current landice/Dockerfile already produces the runtime image as its final stage, so this step is not needed for a normal build. Kept as a reference for standalone runtime images. Create Dockerfile.runtime:
FROM containers.mathworks.com/matlab-runtime:r2024b
# Copy compiled application
COPY --from=matlab-landice:r2024b /app/landice/LandIce/for_testing /opt/landice
# Set environment variables
ENV LD_LIBRARY_PATH="/opt/mcr/v913/runtime/glnxa64:/opt/mcr/v913/bin/glnxa64:/opt/mcr/v913/sys/os/glnxa64:${LD_LIBRARY_PATH}"
# Create working directory
WORKDIR /data
# Set entrypoint to the application
ENTRYPOINT ["/opt/landice/run_LandIce.sh", "/opt/mcr/v913"]
2. Build Runtime Container
docker build -f Dockerfile.runtime -t landice-runtime:latest .
3. Run the Application
docker run --rm \
--shm-size=512M \
-v /path/to/input:/input:ro \
-v /path/to/output:/output \
landice-runtime:latest \
year 2024 \
doy 100
Environment Variables Required
The application expects these environment variables to be set:
OSISAF_FTP_REPROCESSED: FTP path for reprocessed dataOSISAF_FTP_ARCHIVE: FTP path for archive dataOSISAF_FTP_PROD: FTP path for production data
Set these in the Docker run command:
docker run --rm \
--shm-size=512M \
-e OSISAF_FTP_REPROCESSED="ftp://example.com/reprocessed" \
-e OSISAF_FTP_ARCHIVE="ftp://example.com/archive" \
-e OSISAF_FTP_PROD="ftp://example.com/prod" \
-v /path/to/input:/input:ro \
-v /path/to/output:/output \
landice-runtime:latest \
year 2024 \
doy 100
Troubleshooting
License Server Connection Issues
If you get license errors, ensure:
- The container can reach the license server
- The license server hostname is resolvable
- Port 7282 is accessible
Memory Issues
If MATLAB crashes or behaves unexpectedly, increase shared memory:
docker run -it --rm --shm-size=4gb matlab-landice:r2024b
Compilation Errors
If compilation fails, check:
- All source files are present in the container
- The project file paths are correct
- Required toolboxes are installed
Verify toolboxes:
docker run --rm matlab-landice:r2024b matlab -batch "ver"
Deployment
Overview
This guide explains how to deploy the compiled LandIce MATLAB application in a lightweight runtime container that only includes the MATLAB Runtime (MCR), not the full MATLAB installation.
The container uses fixed paths:
/input- Mount your input data here (read-only)/output- Mount your output directory here- Only
yearanddoyarguments are required as positional parameters - the entrypoint automatically maps/inputand/outputdirectories
Building Containers
Build with build_module.sh from the mur/ directory — it handles the base image, license check, platform flag, build context, and the mur-landice:latest tag the pipeline config expects:
cd mur
./build_module.sh landice
Manual Build (Advanced)
Only for custom tags, external CI, or Dockerfile debugging.
IMPORTANT: The Dockerfile references shared utilities from the ../common folder, so the build context must include the parent mur directory. Build from within the landice directory and set the context to the parent (..). The mur-matlab-base:r2024b image must already exist — run ./build_matlab_base.sh from mur/ first.
# Navigate to the landice directory
cd mur/landice
# Build with Docker specifying the platform
docker build --platform linux/amd64 -f Dockerfile -t mur-landice:latest ..
# Or, on macOS with Apple's container tools, build for AMD64
container build --arch amd64 -f Dockerfile -t mur-landice:latest ..
Note: The .. at the end sets the build context to the parent mur directory, which allows the Dockerfile to access both landice/ and common/ folders. When building AMD64 containers on Apple Silicon Macs, the --arch amd64 flag ensures the container is built for x86_64 architecture, which may be required for compatibility with production environments.
Deployment Options
Option 1: Multi-Stage Build (Recommended)
Build and deploy in a single Docker build process:
⚠️ Important License Warning: The build process requires a valid MATLAB license server connection during compilation. If the license server is unreachable or the license cannot be obtained, the build may hang indefinitely at the compilation step. Ensure:
- Your network can reach the MATLAB license servers
- You have valid MATLAB and MATLAB Compiler licenses
- The
network.licfile exists in the parent directory (copy fromnetwork.lic.example)
# Build the multi-stage container
cd mur
./build_module.sh landice
# Run the application
docker run --rm \
--memory=4g \
--memory-reservation=2g \
--memory-swap=6g \
--shm-size=512M \
--cpus="2.0" \
-e MCR_CACHE_ROOT=/tmp/mcr_cache \
-e MCR_CACHE_SIZE=1024M \
-e _JAVA_OPTIONS="-Xmx2048m -Xms512m -XX:+UseG1GC -XX:+UseStringDeduplication" \
-e OSISAF_FTP_ARCHIVE="https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc" \
-e OSISAF_FTP_PROD="https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc" \
-v $(pwd)/tests/in:/input:ro \
-v $(pwd)/tests/out:/output \
mur-landice:latest \
2024 101
Performance Optimization
Shared Memory Configuration (Required)
MATLAB Runtime requires shared memory to be set to 512MB for optimal performance:
docker run --rm \
--shm-size=512M \
mur-landice:latest [arguments]
Memory Settings
For large datasets, adjust Docker memory limits:
docker run --rm \
--shm-size=512M \
--memory="4g" \
--memory-swap="4g" \
--cpus="2" \
mur-landice:latest [arguments]
MCR Cache Settings
Optimize MATLAB Runtime cache:
docker run --rm \
--shm-size=512M \
-e MCR_CACHE_SIZE=2048M \
-e MCR_CACHE_ROOT=/tmp/mcr_cache \
-v /fast/storage/mcr_cache:/tmp/mcr_cache \
mur-landice:latest [arguments]
Monitoring and Logging
Enable Verbose Logging
docker run --rm \
--shm-size=512M \
-e MCR_CACHE_VERBOSE=true \
mur-landice:latest [arguments] 2>&1 | tee landice_$(date +%Y%m%d_%H%M%S).log
Health Check Script
#!/bin/bash
# healthcheck.sh
docker run --rm mur-landice:latest -? > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "LandIce container is healthy"
exit 0
else
echo "LandIce container health check failed"
exit 1
fi
Troubleshooting
Common Issues and Solutions
- Build Hangs During Compilation If the Docker build appears to hang at the
mcccompilation step:# Check license server connectivity from your host telnet ..... # Or test with a simpler MATLAB command first docker run --rm \ -e MLM_LICENSE_FILE={PORT}@{SERVER} \ mathworks/matlab:r2024b \ -batch "disp('License check successful')"Common causes:
- License server unreachable (network/firewall issues)
- No available licenses
- Incorrect license server configuration
- VPN required for license server access
- Missing Libraries
# Check for missing libraries docker run --rm mur-landice:latest ldd /opt/landice/bin/LandIce - Permission Issues
# Ensure output directory is writable docker run --rm \ --shm-size=512M \ --user $(id -u):$(id -g) \ -v /path/to/output:/output \ mur-landice:latest [arguments] - Network Issues
# Test connectivity to the OSI-SAF THREDDS endpoint. Do NOT test # ftp://osisaf.met.no — that host is dead and the probe just hangs # until it times out. docker run --rm mur-landice:latest \ bash -c "curl -sS -o /dev/null -w '%{http_code}\n' -I \ https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/amsr2_conc/2026/09/ice_conc_nh_polstere-100_amsr3_202609131200.nc" - Memory Issues Increase Docker memory allocation or use swap:
docker run --rm \ --shm-size=512M \ --memory="8g" \ --memory-swap="16g" \ mur-landice:latest [arguments]
Security Considerations
- Run as Non-Root User
# Add to Dockerfile.runtime RUN useradd -m -u 1000 landice USER landice - Read-Only Root Filesystem
docker run --rm \ --read-only \ --tmpfs /tmp \ mur-landice:latest [arguments] - Network Isolation Use Docker networks to isolate containers:
docker network create landice-net docker run --rm --network=landice-net mur-landice:latest [arguments]
Reprocessing past dates
Summary
Different grid resolutions (p01 at 0.01° and p011 at 0.011°) have different reprocessing cutoff dates, which means they may pull ice concentration data from different FTP sources for the same calendar date.
Critical Dates
p011 Grid (0.011° / ~1km resolution)
- Cutoff Date: 2006-12-31 (MJD = 54100)
- Dates ≤ 2006-12-31: Use REPROCESSED FTP source (
*_reproc_*.ncfiles) - Dates > 2006-12-31: Use ARCHIVE/PROD FTP source (
*_multi_*.ncfiles)
p01 Grid (0.01° resolution)
- Cutoff Date: 2008-12-31 (MJD = 54831)
- Dates ≤ 2008-12-31: Use REPROCESSED FTP source (
*_reproc_*.ncfiles) - Dates > 2008-12-31: Use ARCHIVE/PROD FTP source (
*_multi_*.ncfiles)
Date Range Analysis
Range 1: Before 2006-12-31
- p011: REPROCESSED source
- p01: REPROCESSED source
- Result: ✅ SAME files - optimization possible
Range 2: 2007-01-01 to 2008-12-31 ⚠️
- p011: ARCHIVE/PROD source
- p01: REPROCESSED source
- Result: ❌ DIFFERENT files - must download separately!
Range 3: After 2008-12-31
- p011: ARCHIVE/PROD source
- p01: ARCHIVE/PROD source
- Result: ✅ SAME files - optimization possible
Why This Matters
The attempted optimization in landice.m (downloading ice files once and reusing for both grids) fails for dates in 2007-2008 because:
- p011 needs
ice_conc_*_polstere-100_multi_YYYYMMDD1200.nc(archive) - p01 needs
ice_conc_*_polstere-100_reproc_YYYYMMDD1200.nc(reprocessed) - These are different files with potentially different data
Solution: Enhanced makeicefiles.m
We've reverted to using makeicefiles.m with enhanced logging:
Changes Made:
- Clear documentation in switch statement (makeicefiles.m:48-67)
- Added comments explaining cutoff dates
- Shows which file type is used for each date range
- Logging of reprocessing cutoff (makeicefiles.m:85-88)
- Prints the cutoff date for the current resolution
- Shows MJD value for debugging
- Enhanced FTP source logging (readosisafice.m:24-51)
- Shows current date's MJD vs cutoff MJD
- Explicitly states which FTP source is selected (REPROCESSED/ARCHIVE/PROD)
- Logs hemisphere being processed
Example Log Output
makeicefiles: resolution - p01
makeicefiles: OSI-SAF reprocessing cutoff for p01 grid: 2008-12-31 (MJD=54831)
readOSISAF: Date 20070701 (nh, MJD=54282) <= reprocessing cutoff (MJD=54831)
readOSISAF: Using REPROCESSED FTP source: https://thredds.met.no/thredds/fileServer/osisaf/met.no/reprocessed/ice/conc
vs
makeicefiles: resolution - p011
makeicefiles: OSI-SAF reprocessing cutoff for p011 grid: 2006-12-31 (MJD=54100)
readOSISAF: Date 20070701 (nh, MJD=54282) > reprocessing cutoff (MJD=54100), <= archive cutoff (MJD=Inf)
readOSISAF: Using ARCHIVE FTP source: https://thredds.met.no/thredds/fileServer/osisaf/met.no/ice/conc
Notice how the same date (2007-07-01) triggers different FTP sources for p01 vs p011!
Files Modified
src/makeicefiles.m- Added comments and logging for cutoff datessrc/readosisafice.m- Enhanced FTP source selection logging
Recommendation
Continue using makeicefiles.m for each resolution separately.
While there is theoretical efficiency to be gained for ~90% of dates (outside 2007-2008 range), the added complexity of a "smart" hybrid system is not worth the risk of incorrect results. The current approach is:
- ✅ Always correct
- ✅ Clear and maintainable
- ✅ Self-documenting via enhanced logs
- ✅ Easy to debug when issues arise