Last active
March 14, 2023 19:37
-
-
Save steigr/8f02f4a135e8250ef4c8a70c18b4d561 to your computer and use it in GitHub Desktop.
Shellscript to build Rails Application Container real fast build time, once built
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
_docker_entrypoint() { | |
cat <<'_docker_entrypoint' | |
#!/usr/bin/env bash | |
# error handling | |
set -eo pipefail | |
trap exit exit | |
pidof bundle >/dev/null 2>&1 && APP_IS_RUNNING=1 | |
# Debugging | |
[[ -z "$APP_IS_RUNNING" ]] || ( [[ -z "$TRACE" ]] || set -x ) | |
[[ -z "$APP_IS_RUNNING" ]] || ( [[ -z "$TRACE" ]] || printenv ) | |
pidof tini >/dev/null 2>&1 || exec tini -- "$0" "$@" | |
# global settings | |
vars() { | |
export RAILS_USER=${RAILS_USER:-application} | |
export LOADER_TITLE=${LOADER_TITLE:-Application} | |
} | |
# create runtime directories | |
prepare_fs() { | |
mkdir -p /app/tmp/sockets \ | |
/app/tmp/pids | |
} | |
# set application secret | |
prepare_secret() { | |
export SECRET_KEY_BASE=${SECRET_KEY_BASE:-$(bundle exec rake secret)} | |
} | |
# check if postgresql instance has been linked, return if not, else export DATABASE_URL | |
use_postgresql() { | |
printenv | grep -q '_ENV_PG_VERSION=' || return 0 | |
DB_SERVER=$(printenv | grep '_ENV_PG_VERSION=' | sed -e 's/_ENV_PG_VERSION=.*//' | tr '[:upper:]' '[:lower:]' || true) | |
DB_PORT=$(printenv | grep "^$(echo $DB_SERVER | tr '[:lower:]' '[:upper:]')_PORT=" | cut -f2- -d= | rev | cut -f1 -d: | rev || true) | |
DB_USER=$(printenv | grep "^$(echo $DB_SERVER | tr '[:lower:]' '[:upper:]')_ENV_POSTGRES_USER"=| cut -f2- -d= || true) | |
DB_PASSWORD=$(printenv | grep "^$(echo $DB_SERVER | tr '[:lower:]' '[:upper:]')_ENV_POSTGRES_PASSWORD="| cut -f2- -d= || true ) | |
DB_NAME=$(printenv | grep "^$(echo $DB_SERVER | tr '[:lower:]' '[:upper:]')_ENV_DASHBOARD_DB=" | cut -f2- -d= || true) | |
DB_SERVER=${DB_SERVER:-postgres} | |
DB_PORT=${DB_PORT:-5432} | |
DB_USER=${DB_USER:-postgres} | |
DB_PASSWORD=${DB_PASSWORD:-postgres} | |
DB_NAME=${DB_NAME:-dashboard_$(echo $RAILS_ENV | tr '[:upper:]' '[:lower:]')} | |
export DATABASE_URL="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_SERVER}:${DB_PORT}/${DB_NAME}?pool=${RAILS_MAX_THREADS:-10}" | |
} | |
can_load_schema() { | |
bundle exec rake db:check_protected_environments >/dev/null 2>&1 | |
} | |
# prepare database | |
prepare_database() { | |
bundle exec rake db:create || true | |
can_load_schema && bundle exec rake db:schema:load || true | |
bundle exec rake db:migrate || true | |
bundle exec rake db:seed || true | |
} | |
# precompile assets (js,css,images) | |
prepare_assets() { | |
bundle exec rake assets:precompile | |
} | |
cpu_count() { | |
grep -c processor /proc/cpuinfo | |
} | |
set_worker_count() { | |
export WEB_CONCURRENCY=$(cpu_count) | |
export RAILS_MAX_THREADS=$(( WEB_CONCURRENCY * 4 )) | |
[[ $RAILS_MAX_THREADS -ge 5 ]] || export RAILS_MAX_THREADS=5 | |
} | |
app_is_running() { | |
test -S /app/tmp/sockets/puma.sock | |
} | |
# wait for app server socket to appear, then start the frontend webserver | |
start_nginx() { | |
echo "Starting Nginx" > /proc/1/fd/2 | |
nginx -g 'daemon on;' | |
} | |
# wait for app server socket to appear, then start the frontend webserver | |
start_placeholder() { | |
bundle exec ruby << '_site_builder' | |
TITLE=(ENV["LOADER_TITLE"]||"Rails application") | |
require "pathname" | |
require "nokogiri" | |
require "erb" | |
module Rails; def self.version; Gem::Specification.find_by_name("rails").version.to_s; end; end | |
site = Nokogiri::XML.parse(ERB.new(File.read(Pathname.new(Gem::Specification.find_by_name("railties").gem_dir).join("lib","rails","templates","rails","welcome","index.html.erb"))).result(binding)) | |
site.xpath("//h1").first.swap("<h1>#{TITLE} is loading ...</h1>") | |
site.xpath("//title").after("<meta http-equiv='refresh' content='5'>") | |
File.write("/tmp/loader.html",site.to_s) | |
_site_builder | |
ruby <<'_loader' | |
puts "Starting Loading-Page" | |
$stdout.reopen("/dev/null","w") | |
$stderr.reopen("/dev/null","w") | |
require "securerandom" | |
ENV["LOADER_SECRET"]||=SecureRandom.hex(16) | |
server = fork do | |
require "sinatra" | |
site=File.read("/tmp/loader.html") | |
File.unlink("/tmp/loader.html") | |
set(:server, "webrick") | |
get("/.loader/is-reachable/#{ENV["LOADER_SECRET"]}"){ENV["LOADER_SECRET"]} | |
get("/*"){site} | |
end | |
fork do | |
require "net/http" | |
loop do | |
sleep 1 | |
response = Net::HTTP.new("localhost",port=(ENV["RAILS_PORT"]||80)).get("/.loader/is-reachable/#{ENV["LOADER_SECRET"]}") rescue nil | |
next if response.nil? | |
next if response.code.to_i >= 500 | |
break if response.code != '200' | |
next if response.body.strip == ENV["LOADER_SECRET"] | |
end | |
Net::HTTP.new("localhost",port=(ENV["RAILS_PORT"]||80)).get("/") | |
sleep 2 | |
`kill #{$$} #{server}` | |
end | |
_loader | |
} | |
# start the application server | |
start_puma() { | |
exec gosu ${RAILS_USER} bundle exec puma --environment $RAILS_ENV --bind unix:///app/tmp/sockets/puma.sock | |
} | |
has_docker_socket(){ | |
test -S /var/run/docker.sock | |
} | |
allow_docker_access() { | |
docker_gid=$(stat -c %g /var/run/docker.sock) | |
addgroup --§ --gid=$docker_gid docker || true | |
docker_group=$(stat -c %G /var/run/docker.sock) | |
adduser ${RAILS_USER} $docker_group | |
} | |
create_user() { | |
adduser --system --home=/app/tmp --shell=/bin/bash --group ${RAILS_USER} | |
} | |
set_permissions() { | |
chown -R ${RAILS_USER} /app/tmp | |
chown -R ${RAILS_USER} /app/log | |
} | |
# put it all together | |
main() { | |
use_postgresql | |
if [[ -z "$APP_IS_RUNNING" ]]; then | |
[[ -n "$@" ]] || start_placeholder | |
[[ -n "$@" ]] || start_nginx | |
create_user | |
has_docker_socket \ | |
&& allow_docker_access \ | |
|| true | |
set_permissions | |
set_worker_count | |
prepare_fs | |
prepare_secret | |
prepare_database | |
if [[ -z "$@" ]]; then | |
prepare_assets | |
start_puma | |
fi | |
fi | |
case "$1" in | |
db:migrate|db:create|db:schema:load|console|server) | |
set -- rails "$@" | |
;; | |
*) | |
set -- "$@" | |
;; | |
esac | |
exec "$@" | |
} | |
vars | |
main "$@" | |
_docker_entrypoint | |
} | |
cat <<'_Dockerfile' | sed -e "s#@@ENTRYPOINT_B64@@#$(_docker_entrypoint | base64)#g" > Dockerfile | |
FROM ruby | |
ARG TINI_VERSION=v0.13.0 | |
RUN curl -sL https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini \ | |
| install -m 0755 -o root -g root /dev/stdin /bin/tini | |
ARG GOSU_VERSION=1.10 | |
RUN curl -sL https://github.com/tianon/gosu/releases/download/${GOSU_VERSION}/gosu-amd64 \ | |
| install -m 0755 -o root -g root /dev/stdin /bin/gosu | |
ARG NODEJS_VERSION=v7.2.0 | |
RUN curl -sL https://nodejs.org/dist/${NODEJS_VERSION}/node-${NODEJS_VERSION}-linux-x64.tar.xz \ | |
| tar -J -x -C /usr/local --strip-components=1 | |
ENV RAILS_LOG_TO_STDOUT=true | |
ENV RAILS_ENV=production | |
RUN export DEBIAN_FRONTEND=noninteractive \ | |
&& export DEBIAN_RELEASE=$(grep "debian.org" /etc/apt/sources.list | head -1 | awk '{print $3}') \ | |
&& echo deb http://nginx.org/packages/mainline/debian/ $DEBIAN_RELEASE nginx >> /etc/apt/sources.list.d/nginx.list \ | |
&& curl -sL http://nginx.org/keys/nginx_signing.key | apt-key add - \ | |
&& apt-get update \ | |
&& apt-get dist-upgrade -y \ | |
&& apt-get install -y nginx \ | |
&& apt-get autoremove -y \ | |
&& rm -rf /var/lib/apt/lists/* \ | |
&& find /etc/nginx -name '*.conf' -print -delete | |
RUN printf 'worker_processes 2;\nevents {\n worker_connections 1024;\n}\nhttp {\nerror_log /proc/1/fd/2;\naccess_log /proc/1/fd/1;\ninclude mime.types;\ndefault_type application/octet-stream;\nsendfile on;\nkeepalive_timeout 65;\ninclude /etc/nginx/conf.d/*.conf;\n}' | install -D -m 0644 -o root -g root /dev/stdin /etc/nginx/nginx.conf | |
RUN printf 'upstream application { server unix:/app/tmp/sockets/puma.sock fail_timeout=0; server 127.0.0.1:4567 backup; }\nserver { listen 80; listen [::]:80; root /app/public; try_files $uri/index.html $uri @application; location @application { proxy_pass http://application; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; } error_page 500 502 503 504 /500.html; client_max_body_size 4G; keepalive_timeout 10; }' | install -D -m 0644 -o root -g root /dev/stdin /etc/nginx/conf.d/rails.conf | |
ENTRYPOINT ["application"] | |
EXPOSE 80/tcp | |
RUN gem install --no-ri --no-rdoc bundler | |
ENV BUNDLE_GITHUB__HTTPS=true | |
RUN printf "\ | |
source 'https://rubygems.org'\n\ | |
gem 'sinatra'\n" \ | |
| tee /tmp/Gemfile \ | |
&& jobs=$(grep -c ^processor /proc/cpuinfo) \ | |
&& cd /tmp \ | |
&& ( until sh -cx "bundle install --jobs=$jobs"; do sleep 1; done ) \ | |
&& rm /tmp/Gemfile* | |
# ARG RAILS_VERSION="'>=5', '<5.1'" | |
ARG RAILS_VERSION="github: 'rails'" | |
RUN printf "\ | |
source 'https://rubygems.org'\n\ | |
gem 'byebug', platform: :mri\n\ | |
gem 'rails', $RAILS_VERSION\n\ | |
gem 'puma'\n\ | |
gem 'sass-rails'\n\ | |
gem 'uglifier'\n\ | |
gem 'coffee-rails'\n\ | |
gem 'jquery-rails'\n\ | |
gem 'turbolinks'\n\ | |
gem 'jbuilder'\n\ | |
gem 'bcrypt'\n\ | |
gem 'versionist'\n\ | |
gem 'dalli'\n\ | |
gem 'bootstrap-sass'\n\ | |
gem 'slim'\n\ | |
gem 'slim-rails'\n" \ | |
| tee /tmp/Gemfile \ | |
&& jobs=$(grep -c ^processor /proc/cpuinfo) \ | |
&& cd /tmp \ | |
&& ( until sh -cx "bundle install --jobs=$jobs"; do sleep 1; done ) \ | |
&& rm /tmp/Gemfile* | |
WORKDIR /app | |
ADD Rakefile /app/Rakefile | |
ADD config.ru /app/config.ru | |
ADD Gemfile /app/Gemfile | |
ADD Gemfile.lock /app/Gemfile.lock | |
RUN jobs=$(grep -c ^processor /proc/cpuinfo) \ | |
&& ( until sh -cx "bundle install --jobs=$jobs --without development test"; do sleep 1; done ) \ | |
&& mkdir -p log tmp/pids tmp/sockets | |
RUN printf '@@ENTRYPOINT_B64@@' | base64 -d | install -D -m 0755 -o root -g root /dev/stdin /bin/application | |
ADD bin /app/bin | |
ADD public /app/public | |
ADD db /app/db | |
ADD lib /app/lib | |
ADD config /app/config | |
ADD app /app/app | |
_Dockerfile | |
trap 'rm Dockerfile' exit | |
if [[ "$1" = "build" ]]; then | |
shift | |
docker build --tag="$(whoami)/$(basename $PWD)" "$@" | |
echo "$0 run --rm --publish=8080:80 --name='$(basename $PWD)' '$(whoami)/$(basename $PWD)'" | |
else | |
docker "$@" | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment