WarpBuild LogoWarpBuild Docs

Ruby with Bundler

Best practices for Dockerfile for Ruby with Bundler

🐳 Annotated Dockerfile for Ruby with Bundler:

# Use a specific version of Ruby from the official Ruby image repository
FROM ruby:3.3-slim-bookworm AS base
 
# --------------------------------------
# Stage 1: Builder - installs dependencies and prepares the environment
# --------------------------------------
FROM base AS builder
 
# Install system dependencies required for gem compilation
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    git \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*
 
# Set working directory
WORKDIR /app
 
# Copy Gemfile and lockfile first for better caching
COPY Gemfile Gemfile.lock ./
 
# Install gems with caching
RUN --mount=type=cache,target=/usr/local/bundle \
    bundle install && \
    bundle clean --force && \
    rm -rf /usr/local/bundle/cache/*.gem && \
    find /usr/local/bundle -name "*.c" -delete && \
    find /usr/local/bundle -name "*.o" -delete
 
# Copy the rest of the application code
COPY . /app
 
# --------------------------------------
# Stage 2: Final production image
# --------------------------------------
FROM base
 
# Install runtime dependencies only
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    tzdata \
    && rm -rf /var/lib/apt/lists/*
 
# Create a non-root user to run the application
RUN groupadd -r appuser && useradd -r -g appuser appuser
 
# Set working directory
WORKDIR /app
 
# Copy gems from builder stage
COPY --from=builder /usr/local/bundle /usr/local/bundle
 
# Copy application code
COPY --from=builder /app /app
 
# Set ownership for application files
RUN chown -R appuser:appuser /app
 
# Switch to non-root user
USER appuser
 
# Expose port 4567 for the sinatra application
EXPOSE 4567
 
# Command to run the application
CMD ["bundle", "exec", "ruby", "app.rb", "-o", "0.0.0.0"]

🔍 Why these are best practices:

✅ Multi-stage builds

  • Separates build environment (with compilers and dev dependencies) from runtime environment.
  • Significantly reduces final image size by not including build tools in production.
  • Improves security by having fewer binaries in the production image.

✅ Bundler optimization

  • BUNDLE_WITHOUT excludes development and test gems from production.
  • BUNDLE_DEPLOYMENT enables deployment mode for consistent environments.
  • BUNDLE_JOBS accelerates gem installation by using multiple cores.
  • BUNDLE_RETRY adds resilience to network issues during installation.

✅ Cleanup operations

  • Removes build artifacts and temporary files to reduce image size.
  • Deletes gem caches, source files, and object files that aren't needed at runtime.
  • Uses apt-get clean and rm -rf /var/lib/apt/lists/* to remove package metadata.

✅ Asset precompilation

  • Precompiles assets during build phase for Rails applications.
  • Ensures assets are optimized and ready to serve in production.
  • Uses a dummy SECRET_KEY_BASE for precompilation to avoid environment variable requirements.

✅ Non-root user

  • Runs the application as a non-privileged user for enhanced security.
  • Follows container security best practices to minimize potential damage from vulnerabilities.

🚀 Additional Dockerfile best practices you can adopt:

Configure Redis/Sidekiq

For applications using background processing with Sidekiq:

# Final stage CMD for Sidekiq worker
CMD ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]

Add health checks

Monitor application health and enable automatic container recovery:

HEALTHCHECK --interval=30s --timeout=5s --start-period=30s \
  CMD curl -f http://localhost:3000/health || exit 1

Use .dockerignore

Exclude unnecessary files from your Docker build context:

.git
.github
.gitignore
log/*
tmp/*
spec/*
test/*
vendor/*
*.md
!README.md
.DS_Store
.env*
.dockerignore
docker-compose*

Configure database migrations

For Rails applications, consider adding an entrypoint script to handle migrations:

# Create entrypoint.sh
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

With entrypoint.sh:

#!/bin/bash
set -e
 
# Run pending migrations if any
bundle exec rake db:migrate
 
# Then exec the container's main process (what's set as CMD)
exec "$@"

Optimize for boot time

For faster application startup:

# Add bootsnap for faster boot
RUN bundle exec bootsnap precompile --gemfile app/ lib/

Consider using jemalloc for better memory management

For high-traffic Ruby applications:

# Install jemalloc in the final stage
RUN apt-get update && apt-get install -y --no-install-recommends \
    libjemalloc2 \
    && rm -rf /var/lib/apt/lists/*
 
# Enable jemalloc
ENV LD_PRELOAD="libjemalloc.so.2"

By following these practices, you'll create Docker images for your Ruby applications that are efficient, secure, and optimized for production environments. These approaches will help ensure consistent deployment, faster performance, and better resource utilization for your Ruby applications.

Last updated on