SERVERLESS RUBY
Build and Deploy AWS Lambda Functions from Scratch

Chapter 3: Docker, ECR, and Ruby Lambda Basics

Write Ruby. Package with Docker. Run Anywhere.

We're about to build your first real AWS Lambda function in Ruby. But before we touch AWS, we need to get your development environment set up with Docker, the key to making Ruby work seamlessly with Lambda.

If you've never touched Docker before, don't worry. You'll get everything you need here, just enough to be dangerous.


Why Docker for Ruby Lambdas?

AWS gives you two ways to package and deploy Lambda functions: you can upload a zip file with your code, or you can use a Docker container image.

In this guide, we'll focus on the container approach. It gives you complete control over your Ruby environment, lets you use any version of Ruby, and makes it easy to include complex dependencies. You just build a Docker image with your Ruby code and push it to AWS.

Here's what you get with Docker:

  • ✅ Modern Ruby versions (3.1, 3.2, etc.)
  • ✅ Any gem you want, including native extensions
  • ✅ Full control over system dependencies
  • ✅ Identical dev and prod environments
With Docker, you stop fighting AWS and start focusing on your code.

Your First Lambda Function – Project Structure

💡 Follow Along: We're about to build a Lambda function template that you'll use throughout this entire guide. I recommend creating this project structure on your local machine and following along with each step, as we'll be extending and reusing this template in every subsequent chapter.

Here's the basic, repeatable structure we'll use for every function in this book:

my_lambda_function/
├── services/
│   └── service.rb                # Core business logic
├── examples/
│   └── example.rb                # Local test runner
├── function.rb                   # Entry point for Lambda
├── boot.rb                       # Loads app dependencies
├── Dockerfile                    # Defines container environment
├── docker-compose.yml            # Local dev/test support
├── Makefile                      # Optional helper commands
├── Gemfile
├── Gemfile.lock

Let's break that down.


🔧 function.rb – Your Lambda Entrypoint

Lambda containers require a file called function.handler.

This is your entrypoint. It should define a handler(event:, context:) method, AWS will call this when your function is triggered.

💡 How this works: Under the hood, AWS maps function.handler to the handler method in your function.rb file. The event parameter contains the trigger data (like an S3 upload event or HTTP request), and context provides runtime information about the Lambda execution environment. For more details on Ruby handler best practices, check out the AWS Lambda Ruby documentation.

# function.rb

require_relative './boot'
require_relative './services/service'

def handler(event:, context:)
  result = Service.call(event)
  puts "Function finished: #{result.inspect}"
  result
end

This file is dead simple. It wires together your code and passes the AWS event to your logic.


🚀 boot.rb – Setting Up the Environment

This file loads your environment. Think of it like a mini version of config/application.rb in Rails.

# boot.rb

require 'bundler/setup'
require 'json'
require 'logger'

Bundler.require

$logger = Logger.new($stdout)

You can add anything here that you want preloaded, libraries, loggers, environment setup, etc.


🔧 services/service.rb – Your Business Logic

This is where the real work happens. Keep your logic here. This keeps your Lambda clean, testable, and easy to reuse.

# services/service.rb

class Service
  def self.call(event)
    $logger.info("Processing event: #{event.inspect}")
    { message: "Hello from Lambda!", input: event }
  end
end

🧪 examples/example.rb – Local Test Runner

This file lets you test your Lambda logic outside of AWS.

# examples/example.rb

require_relative '../boot'
require_relative '../services/service'

event = { "name" => "Zayne", "action" => "test" }
puts Service.call(event)

You can run this locally with ruby examples/example.rb. It's a great sanity check before deploying.


🐳 Your First Dockerfile

Here's a production-ready Dockerfile using Ruby 3.3:


# Use the official AWS Lambda Ruby 3.3 base image
FROM public.ecr.aws/lambda/ruby:3.3

# Set environment
ARG ENVIRONMENT=production
ENV BUNDLE_PATH="/var/task/vendor/bundle"
ENV BUNDLE_WITHOUT="development:test"

# Set working directory to Lambda task root
WORKDIR $LAMBDA_TASK_ROOT

# Install build dependencies
RUN dnf -y update && \
    dnf -y install gcc make ca-certificates && \
    dnf clean all

# Install gems
COPY Gemfile Gemfile.lock ./
RUN bundle install --path vendor/bundle --without development test

# Copy the rest of the code
COPY . .

# Lambda entrypoint
CMD [ "function.handler" ]

💡 Lambda will call function.handler, which is why function.rb defines a handler method.


📦 Gemfile

# Gemfile
source "https://rubygems.org"

# Example gems
gem 'aws-sdk-s3'
gem "json"

group :development do
  gem 'pry'
end

group :test do
  gem 'rspec'
end

Continue reading with full access

Get unlimited access to Serverless Ruby and learn to build production-ready serverless Ruby applications.
📖
Complete guide with 8 chapters and bonus content
💻
Real-world examples with copy-paste code templates
🎯
Step-by-step tutorial and walkthroughs
Pay once to get full access to the book, including all future updates.