Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 7 additions & 15 deletions .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Deploy to production

env:
RAILS_ENV: production
SSH_PRODUCTION_PRIVATE_KEY: ${{ secrets.SSH_PRODUCTION_PRIVATE_KEY }}
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
Expand All @@ -12,9 +11,9 @@ on:
workflow_dispatch:
inputs:
branch:
description: "Branch to deploy"
description: 'Branch to deploy'
required: true
default: "main"
default: 'main'

jobs:
build_and_deploy:
Expand All @@ -29,7 +28,7 @@ jobs:
uses: webfactory/ssh-agent@v0.7.0
with:
ssh-private-key: |
${{ env.SSH_PRODUCTION_PRIVATE_KEY }}
${{ env.SSH_PRIVATE_KEY }}

- name: Install docker buildx
uses: docker/setup-buildx-action@v2
Expand All @@ -49,13 +48,6 @@ jobs:
- name: Install kamal
run: gem install kamal

- name: Run kamal envify targeting production
run: kamal envify

- name: Run Kamal deploy targeting production
env:
KAMAL_ENV: ${{ env.RAILS_ENV }}
RAILS_ENV: ${{ env.RAILS_ENV }}
AWS_ACCOUNT_ID: ${{ env.AWS_ACCOUNT_ID }}
DOCKER_BUILDKIT: 1
run: kamal deploy
- name: Deploy to Production
run: |
kamal deploy
20 changes: 6 additions & 14 deletions .github/workflows/deploy-staging.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: Deploy to staging

env:
RAILS_ENV: staging
SSH_STAGING_PRIVATE_KEY: ${{ secrets.SSH_STAGING_PRIVATE_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Expand All @@ -12,16 +11,16 @@ on:
workflow_dispatch:
inputs:
branch:
description: "Branch to deploy"
description: 'Branch to deploy'
required: true
default: "main"
default: 'main'

jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ inputs.branch }}

Expand Down Expand Up @@ -49,13 +48,6 @@ jobs:
- name: Install kamal
run: gem install kamal

- name: Run kamal envify targeting staging
run: kamal envify -d staging

- name: Run Kamal deploy targeting staging
env:
KAMAL_ENV: ${{ env.RAILS_ENV }}
RAILS_ENV: ${{ env.RAILS_ENV }}
AWS_ACCOUNT_ID: ${{ env.AWS_ACCOUNT_ID }}
DOCKER_BUILDKIT: 1
run: kamal deploy -d staging
- name: Deploy to Staging
run: |
kamal deploy -d staging
20 changes: 20 additions & 0 deletions .kamal/secrets
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.

# Example of extracting secrets from 1password (or another compatible pw manager)
SECRETS=$(kamal secrets fetch --adapter aws_secrets_manager --account default --secret-id production/rails_api)

DB_HOST=$(kamal secrets extract DB_HOST ${SECRETS})
SERVER_HOST=$(kamal secrets extract SERVER_HOST ${SECRETS})
JWT_SECRET=$(kamal secrets extract JWT_SECRET ${SECRETS})
FRONTEND_URL=$(kamal secrets extract FRONTEND_URL ${SECRETS})
DATABASE_URL=$(kamal secrets extract DATABASE_URL ${SECRETS})
SIDEKIQ_REDIS_URL=$(kamal secrets extract SIDEKIQ_REDIS_URL ${SECRETS})
S3_ACCESS_KEY_ID=$(kamal secrets extract S3_ACCESS_KEY_ID ${SECRETS})
S3_ACCESS_KEY=$(kamal secrets extract S3_ACCESS_KEY ${SECRETS})
S3_REGION=$(kamal secrets extract S3_REGION ${SECRETS})
S3_BUCKET=$(kamal secrets extract S3_BUCKET ${SECRETS})
SECRET_KEY_BASE=$(kamal secrets extract SECRET_KEY_BASE ${SECRETS})
POSTGRES_PASSWORD=$(kamal secrets extract POSTGRES_PASSWORD ${SECRETS})

18 changes: 18 additions & 0 deletions .kamal/secrets.staging
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.

# Example of extracting secrets from 1password (or another compatible pw manager)
SECRETS=$(kamal secrets fetch --adapter aws_secrets_manager --account default --secret-id staging/rails_api)

SERVER_HOST=$(kamal secrets extract SERVER_HOST ${SECRETS})
JWT_SECRET=$(kamal secrets extract JWT_SECRET ${SECRETS})
FRONTEND_URL=$(kamal secrets extract FRONTEND_URL ${SECRETS})
DATABASE_URL=$(kamal secrets extract DATABASE_URL ${SECRETS})
SIDEKIQ_REDIS_URL=$(kamal secrets extract SIDEKIQ_REDIS_URL ${SECRETS})
S3_ACCESS_KEY_ID=$(kamal secrets extract S3_ACCESS_KEY_ID ${SECRETS})
S3_ACCESS_KEY=$(kamal secrets extract S3_ACCESS_KEY ${SECRETS})
S3_REGION=$(kamal secrets extract S3_REGION ${SECRETS})
S3_BUCKET=$(kamal secrets extract S3_BUCKET ${SECRETS})
SECRET_KEY_BASE=$(kamal secrets extract SECRET_KEY_BASE ${SECRETS})
POSTGRES_PASSWORD=$(kamal secrets extract POSTGRES_PASSWORD ${SECRETS})
69 changes: 27 additions & 42 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,50 @@

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.3.6
FROM ruby:$RUBY_VERSION-slim as base

ARG RAILS_ENV=production
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base

# Rails app lives here
WORKDIR /rails

# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips postgresql-client && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Set production environment
ENV BUNDLE_DEPLOYMENT="1" \
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development:test" \
RAILS_ENV=$RAILS_ENV

# Update gems and bundler
RUN gem update --system --no-document && \
gem install -N bundler

BUNDLE_WITHOUT="development"

# Throw-away build stage to reduce size of final image
FROM base as build
FROM base AS build

# Install packages needed to build gems
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-essential libpq-dev libvips
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install application gems
COPY --link Gemfile Gemfile.lock .ruby-version ./
RUN --mount=type=cache,id=bld-gem-cache,sharing=locked,target=/srv/vendor \
bundle config set app_config .bundle && \
bundle config set path /srv/vendor && \
bundle install && \
bundle exec bootsnap precompile --gemfile && \
bundle clean && \
mkdir -p vendor && \
bundle config set path vendor && \
cp -ar /srv/vendor .
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile

# Copy application code
COPY --link . .
COPY . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile

# Final stage for app image
FROM base

ARG KAMAL_VERSION

# Install packages needed for deployment
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 curl libvips postgresql-client

# Final stage for app image
FROM base

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
Expand All @@ -66,15 +54,12 @@ COPY --from=build /rails /rails
# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R 1000:1000 db log storage tmp
chown -R rails:rails db log storage tmp
USER 1000:1000

# Set the version of Kamal deployment
ENV KAMAL_VERSION=$KAMAL_VERSION

# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]
# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]
17 changes: 6 additions & 11 deletions bin/bundle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ m = Module.new do
bundler_version = nil
update_index = nil
ARGV.each_with_index do |a, i|
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN)
bundler_version = a
end
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
Expand All @@ -47,7 +47,7 @@ m = Module.new do
def lockfile
lockfile =
case File.basename(gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
when "gems.rb" then gemfile.sub(/\.rb$/, ".locked")
else "#{gemfile}.lock"
end
File.expand_path(lockfile)
Expand All @@ -62,22 +62,17 @@ m = Module.new do

def bundler_requirement
@bundler_requirement ||=
env_var_version || cli_arg_version ||
bundler_requirement_for(lockfile_version)
env_var_version ||
cli_arg_version ||
bundler_requirement_for(lockfile_version)
end

def bundler_requirement_for(version)
return "#{Gem::Requirement.default}.a" unless version

bundler_gem_version = Gem::Version.new(version)

requirement = bundler_gem_version.approximate_recommendation

return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0")

requirement += ".a" if bundler_gem_version.prerelease?

requirement
bundler_gem_version.approximate_recommendation
end

def load_bundler!
Expand Down
15 changes: 7 additions & 8 deletions bin/docker-entrypoint
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
#!/bin/bash -e

# If running the rails server then create or migrate existing database
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
./bin/rails db:prepare
# Enable jemalloc for reduced memory usage and latency.
if [ -z "${LD_PRELOAD+x}" ]; then
LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit)
export LD_PRELOAD
fi

if [ -f tmp/pids/server.pid ]; then
# if there's no process with the server.pid then remove the file
if ! ps $(cat tmp/pids/server.pid) > /dev/null; then
rm tmp/pids/server.pid
fi
# If running the rails server then create or migrate existing database
if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then
./bin/rails db:prepare
fi

exec "${@}"
27 changes: 27 additions & 0 deletions bin/kamal
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

#
# This file was generated by Bundler.
#
# The application 'kamal' is installed as part of a gem, and
# this file is here to facilitate running it.
#

ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)

bundle_binstub = File.expand_path("bundle", __dir__)

if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end

require "rubygems"
require "bundler/setup"

load Gem.bin_path("kamal", "kamal")
14 changes: 6 additions & 8 deletions bin/setup
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env ruby
require "fileutils"

# path to your application root.
APP_ROOT = File.expand_path("..", __dir__)
APP_NAME = "rails-api"

def system!(*args)
system(*args, exception: true)
Expand All @@ -14,7 +14,6 @@ FileUtils.chdir APP_ROOT do
# Add necessary setup steps to this file.

puts "== Installing dependencies =="
system! "gem install bundler --conservative"
system("bundle check") || system!("bundle install")

# puts "\n== Copying sample files =="
Expand All @@ -28,10 +27,9 @@ FileUtils.chdir APP_ROOT do
puts "\n== Removing old logs and tempfiles =="
system! "bin/rails log:clear tmp:clear"

puts "\n== Restarting application server =="
system! "bin/rails restart"

# puts "\n== Configuring puma-dev =="
# system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}"
# system "curl -Is https://#{APP_NAME}.test/up | head -n 1"
unless ARGV.include?("--skip-server")
puts "\n== Starting development server =="
STDOUT.flush # flush the output before exec(2) so that it displays
exec "bin/dev"
end
end
5 changes: 5 additions & 0 deletions bin/thrust
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env ruby
require "rubygems"
require "bundler/setup"

load Gem.bin_path("thruster", "thrust")
Loading