Production Ruby: Deployment and CI/CD
Prerequisites: Building web apps with Rack, Security fundamentals
Getting code from your laptop to a server — reliably, repeatably, and with tests checked on the way — is the last step of building real software.
Continuous integration
CI runs your tests and linter automatically on every push, so broken code is caught before it's merged. With GitHub Actions:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"
bundler-cache: true # runs bundle install and caches gems
- run: bundle exec rubocop
- run: bundle exec rake test
- run: bundle exec bundler-audit check --update
Pin the project's Ruby version in a .ruby-version file (for
example, 3.4.1) so every developer, CI, and production use the same
one; setup-ruby reads it if you leave ruby-version out.
Packaging with Docker
A container image bundles your app, its gems, and the Ruby runtime, so it runs the same everywhere:
# Dockerfile
FROM ruby:3.4-slim AS build
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends build-essential
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true \
&& bundle config set --local without "development test" \
&& bundle install
COPY . .
FROM ruby:3.4-slim
WORKDIR /app
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /app /app
RUN useradd --create-home app
USER app
ENV RUBY_YJIT_ENABLE=1
EXPOSE 9292
CMD ["bundle", "exec", "puma", "-p", "9292"]
- The multi-stage build keeps compilers out of the final image.
- Copying
GemfileandGemfile.lockbefore the rest of the code lets Docker cache the slowbundle installstep. deployment trueinstalls exactly what's locked and fails ifGemfile.lockis out of date; development and test gems are skipped.- Running as a non-root user limits the damage if the app is compromised.
Running in production
- Web server — Puma, configured with a number of workers (processes, for using all CPU cores) and threads per worker (for concurrent I/O).
- Configuration — all environment-specific settings and secrets from environment variables.
- Logs — to standard output, collected by the platform.
- Background jobs — slow work (emails, reports) goes to a job queue such as Sidekiq or Solid Queue, run by separate worker processes.
- Health check — a cheap endpoint such as
/healththat the platform polls to know the app is alive. - Monitoring — an error tracker (such as Sentry or Honeybadger) and basic metrics, so you learn about failures before your users report them.
Deploying safely
- Deploy small changes often; they're easier to debug than big ones.
- Run database migrations in a way that works with both the old and the new code during the rollout.
- Know how to roll back to the previous image quickly.
Tools such as Kamal (from the Rails team) deploy Docker images to your own servers; platforms such as Heroku, Render, and Fly.io run them for you.
Congratulations
You've reached the end of Level 3. Build one of the Advanced Projects, then read What You Should Know Now — and What's Next for where to go from here.