5. Creating custom Rock server images

Goal

Learn how to build custom Rock server Docker images with specific DataSHIELD packages and versions. This allows you to create reproducible, version-controlled profiles that can be shared across deployments.

Why build custom Rock images?

While the previous section showed how to add profiles using the standard obiba/rock:latest image, building custom images provides several advantages:

  • Version control: Pin specific package versions for reproducibility
  • Consistency: Ensure all environments use identical package versions

Prerequisites

  • Docker installed and running
  • Basic understanding of Dockerfiles
  • Understanding of R package dependencies

Understanding the base image

DataSHIELD provides base images that you can extend:

  • datashield/rock-base:6.3-R4.3: Minimal Rock server with R 4.3
  • obiba/rock:latest: Standard Rock just with resourcer
Note

DataSHIELD base images follow the pattern {dsBase-version}-R{R-version}. In the example above, 6.3-R4.3 means dsBase version 6.3 with R version 4.3.

For custom builds, start with rock-base for maximum control over package versions.

Example: Survival analysis profile

Here’s a complete example for creating a survival analysis Rock image:

1) Create the Dockerfile

Dockerfile.survival:

#
# Rock R Server Dockerfile with DataSHIELD Survival profile
#
# Based on: https://github.com/datashield/docker-rock
#

FROM datashield/rock-base:6.3-R4.3

# Define package versions for reproducibility
ENV DSURVIVAL_VERSION v2.3.0-dev

# Rock library path
ENV ROCK_LIB /var/lib/rock/R/library

# Install DataSHIELD packages with specific versions

# dsSurvival (survival analysis functions)
RUN Rscript -e "remotes::install_github('datashield/dsSurvival', ref = '$DSURVIVAL_VERSION', dependencies = TRUE, upgrade = FALSE, lib = '$ROCK_LIB')"

# Fix ownership (Rock runs as non-root user)
RUN chown -R rock $ROCK_LIB

2) Build the image

# Build the image with a descriptive tag
docker build -f Dockerfile.survival -t rock-survival:v2.3.0 .

# Alternative: Build with multiple tags
docker build -f Dockerfile.survival \
  -t rock-survival:v2.3.0 \
  -t rock-survival:latest \
  .

3) Test the image locally

# Run the custom image
docker run -d --name test-survival-rock \
  -p 8085:8085 \
  rock-survival:v2.3.0

# Check that packages are installed
docker exec test-survival-rock Rscript -e "library(dsSurvival); packageVersion('dsSurvival')"

# Clean up
docker stop test-survival-rock
docker rm test-survival-rock

Image management and distribution

Tagging strategy

Use semantic versioning for your custom images:

# Development versions
docker build -t rock-survival:v2.3.0-dev .

# Release versions
docker build -t rock-survival:v2.3.0 .
docker build -t rock-survival:latest .

# Environment-specific tags
docker build -t rock-survival:production .
docker build -t rock-survival:staging .

Pushing to a registry

A Docker registry is a centralized repository for storing and distributing Docker images. It allows you to share images across different environments and with other developers. Docker Hub is the most popular free registry service provided by Docker, offering public repositories for open-source projects and private repositories for proprietary code.

# Tag for your registry
docker tag rock-survival:v2.3.0 your-registry.com/rock-survival:v2.3.0

# Push to registry
docker push your-registry.com/rock-survival:v2.3.0

In the case of using Docker Hub, you can use the following command to push your image to the registry:

docker tag rock-survival:v2.3.0 your-dockerhub-username/rock-survival:v2.3.0
docker push your-dockerhub-username/rock-survival:v2.3.0

Troubleshooting image builds

Common build issues

Package installation failures:

# Add error handling
RUN Rscript -e "
    tryCatch({
        remotes::install_github('datashield/dsBase', lib = '$ROCK_LIB')
    }, error = function(e) {
        cat('Error installing dsBase:', e$message, '\n')
        quit(status = 1)
    })
"

Permission issues:

# Ensure proper ownership at each step
RUN Rscript -e "remotes::install_github('datashield/dsBase', lib = '$ROCK_LIB')" \
    && chown -R rock $ROCK_LIB

Build context too large:

# Check build context size
du -sh .

# Use .dockerignore to exclude large files
echo "*.log" >> .dockerignore
echo "data/" >> .dockerignore

Debugging build failures

# Build with no cache to see all steps
docker build --no-cache -f Dockerfile.survival .

# Interactive debugging
docker run -it --rm datashield/rock-base:6.3-R4.3 /bin/bash

# Check intermediate layers
docker build -f Dockerfile.survival -t debug-build .
docker run -it debug-build /bin/bash

Best practices

  1. Version everything: Pin package versions and base image tags
  2. Use multi-stage builds: Keep final images small
  3. Layer efficiently: Order Dockerfile commands by change frequency
  4. Test thoroughly: Automate testing of built images
  5. Document well: Include package versions and build instructions

References