Crafting and optimising Dockerfiles can be a bit tedious with the edit-build-test cycle, especially when optimising layer caching and working with multi-stage builds. Running commands or clicking buttons on every change—where is our hot reloading?

An answer - Tilt. Tilt’s primary purpose is providing local dev environments for microservice development, like a whole Kubernetes stack or Docker Compose stack.

Tilt is an awesome, rewarding tool which I use regularly. Part of its offering is monitoring Dockerfiles for changes with automatic rebuilds and displaying this in a web UI. Hot reloading for the Dockerfile creator.

Tilt in Action

Introducing a language-specific quirk with a much more niche audience—R Docker images in particular bring another complication. R’s standard install.packages() function does not return an error exit code on package installation failure. It warns instead. The install2.r script provides proper build failures.

Tech Stack#

  • Ubuntu Linux
  • Tilt
  • Docker Engine (with Compose)

The Silent Failure Problem in R Builds#

R package installations can fail without failing the Docker build.

Usually this is a forgotten system package, e.g., RUN apt update && apt install -y unixodbc-dev

In these instances, install.packages() returns successfully with some warning messages somewhere up in the build log, leaving you with a broken image that only fails at runtime.

# This can silently fail
FROM r-base:4.5.1
RUN Rscript -e "install.packages('odbc')"

R Build Silent Fail

The install2.r script from the littler package properly propagates R errors as exit codes when used with the -e parameter:

FROM r-base:4.5.1

# Download install2.r script
RUN wget -q https://github.com/eddelbuettel/littler/raw/master/inst/examples/install2.r \
    -O /usr/local/bin/install2.r && chmod +x /usr/local/bin/install2.r

# This will fail the build if packages don't install
RUN install2.r -e dplyr odbc

R Build Fail Fast

Now missing dependencies cause immediate build failures rather than runtime surprises.

Faster Dockerfile Development with Tilt#

The traditional Docker workflow—edit, build, test, repeat—becomes painful with R’s longer package installation times. Tilt provides hot reloading for Docker builds (and a whole lot more).

This example is R-specific, but the concept is of course applicable when developing any Dockerfile.

First, create a docker-compose.yml file:

# docker-compose.yml
services:
  r-etl:
    build: .
    volumes:
      - ./scripts:/app/scripts

This is essentially boilerplate to get Tilt to provide the functionality we want. If we don’t have Docker Compose, it will ignore the image build, thinking it’s not part of our dev stack.

Then create a Tiltfile:

# Tiltfile
docker_compose('./docker-compose.yml')

Start with tilt up. When you save changes to your Dockerfile, Tilt automatically rebuilds and shows progress in its web UI. The feedback loop is markedly improved.

Tilt Up CLI

For R script changes without full rebuilds, use live_update:

# Existing Tiltfile
docker_build('r-etl', '.', 
    live_update=[
        sync('./scripts', '/app/scripts'),
    ])

Complete Example#

FROM r-base:4.5.1 as r-etl

# Install system dependencies and download install2.r
RUN apt-get update && apt-get install -y \
    unixodbc-dev \
    wget \
    && wget -q https://github.com/eddelbuettel/littler/raw/master/inst/examples/install2.r \
       -O /usr/local/bin/install2.r && chmod +x /usr/local/bin/install2.r \
    && rm -rf /var/lib/apt/lists/*

# Build fails if packages don't install
RUN install2.r -e dplyr odbc

COPY scripts/ /app/scripts/
WORKDIR /app

CMD ["Rscript", "scripts/etl.R"]
# docker-compose.yml
services:
  r-etl:
    build: .
    volumes:
      - ./scripts:/app/scripts
# Tiltfile
docker_compose('./docker-compose.yml')
docker_build('r-etl', '.',
    live_update=[
        sync('./scripts', '/app/scripts'),
    ])

Here’s a simple ETL script that demonstrates the ODBC dependency:

# scripts/etl.R
tryCatch({
  library(odbc)
  cat("✓ ODBC package loaded successfully\n")
}, error = function(e) {
  cat("✗ Failed to load ODBC package:", e$message, "\n")
  quit(status = 1)
})

Conclusion#

Tilt is handy for anyone crafting Dockerfiles regularly, and install2.r reduces friction in the life of an R Dockerfile creator.