Skip to content

Instantly share code, notes, and snippets.

@bradgessler
Last active November 4, 2022 04:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bradgessler/8682a1f7a3eadbd00002a8e37181b947 to your computer and use it in GitHub Desktop.
Save bradgessler/8682a1f7a3eadbd00002a8e37181b947 to your computer and use it in GitHub Desktop.
Fly Rails Deploy
# What this demonstrates:
#
# 1. How a project can reflect on a Gemfile.lock to extract the BUNDLER_VERSION and RUBY_VERSION
# to pass into the Docker build process via ARGV. This minimizes the amount of configuration that
# less technical users have to worry about -- They can just stay in the Gemfile.
# 2. This shows that we could have "Dockerfile"-less projects that load a Dockerfile from a remote
# URL. This could be a "well-known" URL like `https://fly.io/Dockerfiles/ruby` that we set initially
# when somebody runs `fly launch`.
#
# How to use this:
#
# 1. From the root of your ruby project, run `ruby deploy.rb` and it will deploy via your local Dockerfile
# 2. To deploy withour a Dockerfile, remove `Dockerfile` from your project root, then add the following to
# your `fly.toml` file:
#
# ```toml
# [build]
# dockerfile_url = "https://gist.githubusercontent.com/bradgessler/8682a1f7a3eadbd00002a8e37181b947/raw/e1fe200f9155fa591bce09e7b5d2fe5c1acc5a33/Dockerfile"
# ```
# 3. Now run `ruby deploy.rb` and your project will deply from the Dockerfile at that URL.
# 4. When you've had your jollies, put the local `Dockerfile` back and it will deploy from there.
require "net/http"
require "tempfile"
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "toml"
end
def build_args(**args)
args.map{ |key, value| [ "--build-arg", [key, value].join("=") ] }.flatten
end
def docker_url
URI TOML.load_file("fly.toml").dig("build", "dockerfile_url")
end
def remote_dockerfile(url)
Tempfile.create do |file|
Net::HTTP.get_response(url) do |response|
file.write response.body
end
file.flush
yield file
end
end
def try_files(*paths)
paths.find { |path| File.exists? path }
end
lockfile = Bundler::LockfileParser.new(File.read("Gemfile.lock"))
platform = Gem::Platform.new("x86_64-linux")
ruby_version = if lockfile.ruby_version.nil?
fail <<~ERROR
A Ruby version is not specified in the Gemfile.
Set the Ruby version in the Gemfile by adding the following to your Gemfile:
```
ruby #{RUBY_VERSION.inspect}
```
Then run `bundle` and deploy again.
ERROR
else
Bundler::RubyVersion.from_string(lockfile.ruby_version)
end
if lockfile.platforms.include? platform
fail <<~ERROR
Gemfile.lock does not have the platform #{platform.to_s.inspect}.
Add the platform by running `bundle lock --add-platform #{platform.to_s}`, then deploy again.
ERROR
end
remote_dockerfile docker_url do |remote_file|
system *p([
"fly",
"deploy",
"--dockerfile", try_files("Dockerfile", remote_file),
*build_args(
RUBY_VERSION: ruby_version.gem_version,
BUNDLER_VERSION: lockfile.bundler_version
)
])
end
# syntax = docker/dockerfile:experimental
# Dockerfile used to build a deployable image for a Rails application.
# Adjust as required.
#
# Common adjustments you may need to make over time:
# * Modify version numbers for Ruby, Bundler, and other products.
# * Add library packages needed at build time for your gems, node modules.
# * Add deployment packages needed by your application
# * Add (often fake) secrets needed to compile your assets
#######################################################################
# Learn more about the chosen Ruby stack, Fullstaq Ruby, here:
# https://github.com/evilmartians/fullstaq-ruby-docker.
#
# We recommend using the highest patch level for better security and
# performance.
ARG RUBY_VERSION=3.1.2
ARG VARIANT=jemalloc-slim
FROM quay.io/evl.ms/fullstaq-ruby:${RUBY_VERSION}-${VARIANT} as base
LABEL fly_launch_runtime="rails"
ARG BUNDLER_VERSION=2.3.23
ARG RAILS_ENV=production
ENV RAILS_ENV=${RAILS_ENV}
ENV RAILS_SERVE_STATIC_FILES true
ENV RAILS_LOG_TO_STDOUT true
ARG BUNDLE_WITHOUT=development:test
ARG BUNDLE_PATH=vendor/bundle
ENV BUNDLE_PATH ${BUNDLE_PATH}
ENV BUNDLE_WITHOUT ${BUNDLE_WITHOUT}
RUN mkdir /app
WORKDIR /app
RUN mkdir -p tmp/pids
#######################################################################
# install packages only needed at build time
FROM base as build_deps
ARG BUILD_PACKAGES="git build-essential libpq-dev wget vim curl gzip xz-utils libsqlite3-dev"
ENV BUILD_PACKAGES ${BUILD_PACKAGES}
RUN --mount=type=cache,id=dev-apt-cache,sharing=locked,target=/var/cache/apt \
--mount=type=cache,id=dev-apt-lib,sharing=locked,target=/var/lib/apt \
apt-get update -qq && \
apt-get install --no-install-recommends -y ${BUILD_PACKAGES} \
&& rm -rf /var/lib/apt/lists /var/cache/apt/archives
#######################################################################
# install gems
FROM build_deps as gems
RUN gem update --system --no-document && \
gem install -N bundler -v ${BUNDLER_VERSION}
COPY Gemfile* ./
RUN bundle install && rm -rf vendor/bundle/ruby/*/cache
#######################################################################
# install deployment packages
FROM base
ARG DEPLOY_PACKAGES="postgresql-client file vim curl gzip libsqlite3-0"
ENV DEPLOY_PACKAGES=${DEPLOY_PACKAGES}
RUN --mount=type=cache,id=prod-apt-cache,sharing=locked,target=/var/cache/apt \
--mount=type=cache,id=prod-apt-lib,sharing=locked,target=/var/lib/apt \
apt-get update -qq && \
apt-get install --no-install-recommends -y \
${DEPLOY_PACKAGES} \
&& rm -rf /var/lib/apt/lists /var/cache/apt/archives
# copy installed gems
COPY --from=gems /app /app
COPY --from=gems /usr/lib/fullstaq-ruby/versions /usr/lib/fullstaq-ruby/versions
COPY --from=gems /usr/local/bundle /usr/local/bundle
#######################################################################
# Deploy your application
COPY . .
# Adjust binstubs to run on Linux and set current working directory
RUN chmod +x /app/bin/* && \
sed -i 's/ruby.exe/ruby/' /app/bin/* && \
sed -i '/^#!/aDir.chdir File.expand_path("..", __dir__)' /app/bin/*
# The following enable assets to precompile on the build server. Adjust
# as necessary. If no combination works for you, see:
# https://fly.io/docs/rails/getting-started/existing/#access-to-environment-variables-at-build-time
ENV SECRET_KEY_BASE 1
# ENV AWS_ACCESS_KEY_ID=1
# ENV AWS_SECRET_ACCESS_KEY=1
# Run build task defined in lib/tasks/fly.rake
ARG BUILD_COMMAND="bin/rails fly:build"
RUN ${BUILD_COMMAND}
# Default server start instructions. Generally Overridden by fly.toml.
ENV PORT 8080
ARG SERVER_COMMAND="bin/rails fly:server"
ENV SERVER_COMMAND ${SERVER_COMMAND}
CMD ${SERVER_COMMAND}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment