├── .circleci └── config.yml ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── VERSION ├── base ├── Dockerfile ├── UTC ├── appserver.sh ├── apt.conf ├── bundle_config ├── clockwork.rb ├── clockwork.sh ├── custom-services.sh ├── db_migrate.sh ├── google-chrome.list ├── puma.rb ├── rails-assets.sh ├── sidekiq.sh ├── sidekiq.yml ├── wait-for-syslog.sh └── yarn.list ├── build.sh ├── deploy.sh ├── onbuild └── Dockerfile ├── release.sh ├── test.sh └── test ├── docker-compose.yml ├── files ├── Dockerfile ├── app │ ├── controllers │ │ └── test_controller.rb │ ├── models │ │ └── post.rb │ └── worker │ │ ├── clockwork_test_worker.rb │ │ └── test_worker.rb ├── bin │ └── services │ │ └── test_service ├── config │ ├── clockwork.rb │ └── puma.rb ├── db │ └── migrate │ │ └── 1_create_posts.rb ├── vendor │ └── vendor-gem │ │ └── vendor-gem.gemspec └── wait_for_postgres.sh └── template.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/ruby:2.7.3-node 6 | steps: 7 | - setup_remote_docker 8 | 9 | - run: 10 | name: Ruby version 11 | command: ruby -v 12 | 13 | - run: 14 | name: Install Docker Machine 15 | command: | 16 | base=https://github.com/docker/machine/releases/download/v0.16.0 && 17 | curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine && 18 | sudo install /tmp/docker-machine /usr/local/bin/docker-machine 19 | 20 | - checkout 21 | 22 | - run: 23 | name: Build image 24 | command: $(pwd)/build.sh 25 | 26 | - run: 27 | name: Run tests 28 | command: $(pwd)/test.sh 29 | 30 | - deploy: 31 | command: | 32 | if [ "${CIRCLE_BRANCH}" == "master" ]; then 33 | ./deploy.sh 34 | fi 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | test/testapp 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Metrics/LineLength: 2 | Max: 100 3 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.5.3 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.8.5 (2021-12-09) 4 | 5 | - Explicitly set sidekiq environment to `RAILS_ENV` 6 | 7 | ## 2.8.4 (2019-07-04) 8 | 9 | - Implement workaround for `HOME` being ignored in `phusion/baseimage` 10 | 11 | ## 2.8.3 (2019-05-24) 12 | 13 | - Port to CircleCI 2.0 14 | - Make it work with `bundle package` (run before building the container), so 15 | fetching gems from within the container can be avoided. This is useful if you 16 | want to use dependencies from private repositories and don't want to give 17 | your container itself access to them. 18 | 19 | ## 2.8.2 (2019-02-05) 20 | 21 | - Pass along `BUGSNAG_API_KEY` and `BUGSNAG_APP_VERSION` to asset precompiling 22 | step 23 | - Update rubygems (3.0.2) 24 | - Update bundler (1.8.2) 25 | 26 | ## 2.8.1 (2018-11-21) 27 | 28 | - Upgrade Ruby (2.5.3) 29 | - Upgrade rubygems (2.7.8) 30 | - Upgrade bundler (1.17.1) 31 | 32 | ## 2.7.3 (2018-02-15) 33 | 34 | - Add `file` command 35 | 36 | - Upgrade Ruby (2.5.0) 37 | - Upgrade rubygems (2.7.4) 38 | - Upgrade bundler (1.16.1) 39 | - Upgrade NodeJS (8.9.4 LTS) 40 | - Upgrade phusion/baseimage (0.10.0) 41 | 42 | ## 2.7.2 (2018-01-25) 43 | 44 | - Introduce ruby-2.4 version tag 45 | 46 | ## 2.7.1 (2018-01-02) 47 | 48 | - Upgrade Ruby (2.4.3) 49 | - Upgrade rubygems (2.6.14) 50 | - Upgrade bundler (1.16.1) 51 | 52 | ## 2.7.0 (2017-12-22) 53 | 54 | - Allow to run additional application services 55 | 56 | ## 2.6.2 (2017-09-13) 57 | 58 | - Add `LIBRATO_AUTORUN` to sidekiq environment 59 | 60 | ## 2.6.1 (2017-07-20) 61 | 62 | - Close Active Record connections [PR #15](https://github.com/ComboStrikeHQ/docker-rails/pull/15) 63 | 64 | ## 2.6.0 (2017-01-26) 65 | 66 | - Upgrade rubygems 67 | - Upgrade bundler 68 | - Upgrade bower 69 | - Switch to NodeJS LTS 70 | - Add support for vendored gems 71 | 72 | ## 2.5.0 (2016-09-22) 73 | 74 | - Add support for Rails 5 native 12-factor config 75 | - Remove puma request logging 76 | - Wait for syslog-ng to start 77 | 78 | ## 2.4.2 (2016-09-19) 79 | 80 | - Fix application server logs missing 81 | 82 | ## 2.4.1 (2016-09-01) 83 | 84 | - Fix asset precompilation when pg gem is not present 85 | 86 | ## 2.4.0 (2016-08-31) 87 | 88 | - Add support for mysql2 gem 89 | - Clean up README 90 | 91 | ## 2.3.0 (2016-07-26) 92 | 93 | - Upgrade baseimage, rubygems, bundler and node 94 | 95 | ## 2.2.1 (2016-06-03) 96 | 97 | - Add proper exit handling for Clockwork 98 | 99 | ## 2.2.0 (2016-05-18) 100 | 101 | - Reduce number of layers in ONBUILD Dockerfile 102 | - Upgrade ruby, rubygems, bundler and nodejs 103 | - Fix graceful shutdown for Puma workers 104 | 105 | ## 2.1.1 (2016-05-13) 106 | 107 | - Allow writing to app directory during ONBUILD 108 | 109 | ## 2.1.0 (2016-04-06) 110 | 111 | - Upgrade rubygems, nodejs and bower 112 | - Do not fail in puma boot when there is no ActiveRecord 113 | 114 | ## 2.0.1 (2016-02-08) 115 | 116 | - Fix bug where everything is logged twice 117 | 118 | ## 2.0.0 (2016-02-08) BREAKING CHANGES, see README 119 | 120 | - Remove logentries and NewRelic server monitor 121 | 122 | ## 1.6.0 (2016-02-02) 123 | 124 | - Upgrade bower to 1.7.6 125 | - Upgrade rubygems to 2.5.2 126 | - Upgrade nodejs to 5.5.0 127 | 128 | ## 1.5.0 (2016-01-15) 129 | 130 | - Upgrade to Ruby 2.3.0 131 | - Upgrade to nodejs 5.3.1 and bower 1.7.2 132 | - Ruby: Enable shared lib and remove static lib 133 | - Speed up tests by removing one `bundle install` call 134 | 135 | ## 1.4.0 (2015-12-23) 136 | 137 | - Allow writing to vendor directory during ONBUILD 138 | - Set HOME in ONBUILD Dockerfile 139 | - Add bower 140 | - Use recent nodejs version 141 | - Update to baseimage-docker 0.9.18 142 | - Remove manual gzipping of assets - newer sprockets versions do that automatically 143 | - Lock rubygems and bundler version 144 | 145 | ## 1.3.0 (2015-12-17) 146 | 147 | - Upgrade to Ruby 2.2.4 148 | 149 | ## 1.2.2 (2015-11-27) 150 | 151 | - Make some ONBUILD commands regular ones 152 | 153 | ## 1.2.1 (2015-11-12) 154 | 155 | - Add prefix to docker tags (for development only) 156 | - Use COPY in ONBUILD Dockerfile 157 | 158 | ## 1.2.0 (2015-10-28) 159 | 160 | - Add gzip compression for asset files 161 | 162 | ## 1.1.1 (2015-10-10) 163 | 164 | - Also log stderr output for all processes 165 | 166 | ## 1.1.0 (2015-10-09) 167 | 168 | - Add clockwork support 169 | 170 | ## 1.0.2 (2015-10-09) 171 | 172 | - Fix sidekiq default queues precedence 173 | 174 | ## 1.0.1 (2015-10-08) 175 | 176 | - Fix custom `puma.rb` configuration 177 | 178 | ## 1.0.0 (2015-10-08) 179 | 180 | - Initial release 181 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 ComboStrike GmbH 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker-Rails 2 | [![Circle CI](https://circleci.com/gh/ComboStrikeHQ/docker-rails.svg?style=svg)] 3 | (https://circleci.com/gh/ComboStrikeHQ/docker-rails) 4 | 5 | An opinionated docker image for running Rails apps in production. 6 | 7 | Uses Puma, Sidekiq, Clockwork and rails_migrate_mutex. 8 | Everything is optional. 9 | 10 | ## Quick Example 11 | - Create a new Rails app (`$ rails new my_blog`) 12 | - Create a `Dockerfile` in the app root with this content: `FROM combostrikehq/docker-rails:latest` 13 | - Add `gem 'puma'` to the `Gemfile` and run `bundle install` 14 | - Build the docker container: `$ docker build -t my_blog .` 15 | - Run the container: `$ docker run -p 8080:8080 -e RAILS_ENV=development my_blog` 16 | - Open `http://localhost:8080` 17 | - Enjoy! 18 | 19 | This example is for demo purposes only. 20 | Docker-Rails is intended for production use, not local development. 21 | Please read the whole README before using. 22 | 23 | ## Docker Image 24 | The image is built by us, squashed into a single layer and pushed to 25 | [Docker Hub](https://registry.hub.docker.com/u/combostrikehq/docker-rails/). 26 | 27 | To use it in your app, create a `Dockerfile` with the following content: 28 | 29 | ```docker 30 | FROM combostrikehq/docker-rails: 31 | ``` 32 | 33 | and replace `` with the [current version number](https://hub.docker.com/r/combostrikehq/docker-rails/tags/). 34 | You can also use the `latest` version tag which would give you the latest unreleased version. This 35 | might be useful if you want to test against the bleeding edge in your testing/beta environment. 36 | 37 | From version 3.0 docker-rails uses ruby 2.5. If you still use ruby 2.4 you have to use docker-rails 38 | versions 2.x. We also provide `ruby-2.5` and `ruby-2.4` tags that will point to the latest released 39 | docker-rails version for that ruby version. 40 | 41 | Add more docker steps if needed (usually not), build and deploy to your infrastructure. 42 | 43 | We use [eb_deployer](https://github.com/ThoughtWorksStudios/eb_deployer) to deploy our apps 44 | to [AWS Elastic Beanstalk](https://aws.amazon.com/de/elasticbeanstalk/). 45 | 46 | ## Contents 47 | 48 | ### Rails App Server / Puma 49 | [Puma](http://puma.io/) is used as the web server for your app. 50 | For this to work properly, please add the following gems to your Gemfile: 51 | 52 | ```ruby 53 | gem 'puma' 54 | gem 'rack-timeout', group: :production 55 | gem 'rails_12factor', group: :production # only for Rails < 5 56 | ``` 57 | 58 | You can configure Puma: 59 | 60 | Environment Variable | Description | Default Value 61 | --- | --- | --- 62 | PUMA_WORKERS | Number of worker processes | [number of CPU cores] 63 | PUMA_MAX_THREADS | Maximum number of threads per process | 16 64 | PUMA_MIN_THREADS | Minimum number of threads per process | PUMA_MAX_THREADS 65 | 66 | You can also create a `config/puma.rb` file in your app that contains additional configuration. 67 | 68 | Have a look at the [Puma configuration file](base/puma.rb) for more details. 69 | 70 | ### Sidekiq 71 | [Sidekiq](http://sidekiq.org/) is used for processing background jobs. A sidekiq process 72 | will be automatically started when the `sidekiq` gem is present in your app. Using sidekiq is 73 | optional. 74 | 75 | You can configure sidekiq: 76 | 77 | Environment Variable | Description | Default Value 78 | --- | --- | --- 79 | SIDEKIQ_THREADS | Number of threads | 16 80 | 81 | You can also create a `config/sidekiq.yml` file in your app that contains additional configuration. 82 | 83 | The default configuration enables the `default` and `mailers` queues. 84 | 85 | ### Clockwork 86 | [Clockwork](https://github.com/tomykaira/clockwork) is used for scheduling background jobs. 87 | A clockwork process will be automatically started when the `clockwork` gem is present in your app. 88 | Using clockwork is optional. 89 | 90 | You need to create a `config/clockwork.rb` file using the following schema: 91 | ```ruby 92 | module Clockwork 93 | every(1.hour, 'my_job') { MyWorker.perform_async } 94 | end 95 | ``` 96 | 97 | Note that the clockwork process is started in every container. We recommend using 98 | [sidekiq-unique-jobs](https://github.com/mhenrixon/sidekiq-unique-jobs) to ensure that jobs 99 | are scheduled only once. 100 | 101 | ### Custom services 102 | 103 | You can run additional services by putting their executable files 104 | in the application `bin/services` folder. 105 | 106 | ### Rails Migrations / rails_migrate_mutex 107 | [rails_migrate_mutex](https://github.com/combostrikehq/rails_migrate_mutex) is used to run Rails 108 | migrations. If you have the gem installed in you app, migrations are automatically run on 109 | container startup. Using rails_migrate_mutex is optional. 110 | 111 | ### Ruby / Packages 112 | The latest ruby version is included in the container. It is compiled from source and uses 113 | [jemalloc](http://www.canonware.com/jemalloc/). The latest rubygems and bundler versions 114 | are installed as well. 115 | 116 | The following tools are installed: `git`, `nodejs`, `imagemagick`, `curl`, `bower`. 117 | Development packages for compiling the following gems are included: 118 | `sqlite3`, `pg`, `mysql2`, `nokogiri`. 119 | 120 | Have a look at the [Base Dockerfile](base/Dockerfile) for more details. 121 | 122 | ### App Installation / Docker ONBUILD 123 | The final docker image has ONBUILD hooks that set up your application. 124 | It installs all gems except those in the `development` and `test` groups. 125 | 126 | If the `sprockets` 127 | gem is present (default for Rails apps), it also precompiles assets. If you need any environment 128 | variables set for compiling assets, you can specify them in a `.env` file in your app root. This 129 | file will be sourced before running the task. Newer versions of sprockets will also automatically 130 | created gzipped versions of the assets. Rails then uses these static gzip files to serve assets. 131 | 132 | Have a look at the [ONBUILD Dockerfile](onbuild/Dockerfile) for more details. 133 | 134 | ### Environment Variables 135 | All environment variables are passed on the application. Both `RAILS_ENV` and `RACK_ENV` are 136 | set to `production` and should not be changed. 137 | 138 | Both `RAILS_SERVE_STATIC_FILES` and 139 | `RAILS_LOG_TO_STDOUT` are set, so you 140 | [don't need to](https://github.com/heroku/rails_12factor#rails-5-and-beyond) 141 | include the `rails_12factor` gem anymore if you're using Rails 5 or above and have updated 142 | your `config/environments/production.rb` file. 143 | 144 | ## License 145 | 146 | MIT, see LICENSE.txt 147 | 148 | ## Contributing 149 | 150 | Please create a new GitHub Issue if you encounter a bug or have a feature request. 151 | 152 | Feel free to fork and submit pull requests! 153 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | VERSION=2.8.5 2 | -------------------------------------------------------------------------------- /base/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM phusion/baseimage:focal-1.0.0 2 | 3 | ENV RUBY_MAJOR "2.7" 4 | ENV RUBY_VERSION "2.7.3" 5 | ENV RUBYGEMS_VERSION "3.3.7" 6 | ENV NODE_VERSION "10.24.1" 7 | ENV BOWER_VERSION "1.8.2" 8 | 9 | ENV APT_PACKAGES " \ 10 | git imagemagick gcc g++ make patch binutils libc6-dev libjemalloc-dev \ 11 | libffi-dev libssl-dev libyaml-dev zlib1g-dev libgmp-dev libxml2-dev \ 12 | libxslt1-dev libpq-dev libreadline-dev libsqlite3-dev libmysqlclient-dev \ 13 | tzdata yarn file google-chrome-stable \ 14 | " 15 | 16 | ENV APT_REMOVE_PACKAGES "anacron cron openssh-server postfix" 17 | 18 | COPY apt.conf /etc/apt/apt.conf.d/local 19 | RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - 20 | COPY yarn.list /etc/apt/sources.list.d 21 | RUN curl -sS https://dl.google.com/linux/linux_signing_key.pub | apt-key add - 22 | COPY google-chrome.list /etc/apt/sources.list.d 23 | RUN apt-get update && apt-get -y dist-upgrade 24 | RUN apt-get install -y --no-install-recommends $APT_PACKAGES 25 | RUN apt-get remove --purge -y $APT_REMOVE_PACKAGES 26 | RUN apt-get autoremove --purge -y 27 | 28 | WORKDIR /tmp 29 | RUN curl -o ruby.tgz \ 30 | "https://cache.ruby-lang.org/pub/ruby/${RUBY_MAJOR}/ruby-${RUBY_VERSION}.tar.gz" && \ 31 | tar -xzf ruby.tgz && \ 32 | cd ruby-${RUBY_VERSION} && \ 33 | ./configure --enable-shared --with-jemalloc --disable-install-doc && \ 34 | make -j4 && \ 35 | make install 36 | 37 | ENV GEM_SPEC_CACHE "/tmp/gemspec" 38 | RUN echo 'gem: --no-document' > $HOME/.gemrc 39 | RUN gem update --system ${RUBYGEMS_VERSION} 40 | 41 | RUN curl https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz \ 42 | |tar -xz -C /usr --strip-components=1 43 | RUN npm install bower@${BOWER_VERSION} -g 44 | 45 | RUN rm /etc/my_init.d/00_regen_ssh_host_keys.sh 46 | RUN rm -r /etc/service/sshd /etc/service/cron 47 | 48 | COPY wait-for-syslog.sh /opt/ 49 | COPY db_migrate.sh /etc/my_init.d/90_db_migrate.sh 50 | COPY sidekiq.sh /etc/service/sidekiq/run 51 | COPY appserver.sh /etc/service/appserver/run 52 | COPY clockwork.sh /etc/service/clockwork/run 53 | 54 | COPY puma.rb /etc/ 55 | COPY sidekiq.yml /etc/ 56 | COPY clockwork.rb /etc/ 57 | 58 | COPY rails-assets.sh /opt/ 59 | COPY custom-services.sh /opt/ 60 | 61 | RUN useradd -m app 62 | COPY bundle_config /home/app/.bundle/config 63 | RUN mkdir /home/app/webapp && chown app:app -R /home/app 64 | 65 | RUN rm -rf /tmp/* /var/tmp/* /var/lib/apt /var/lib/dpkg /usr/share/man /usr/share/doc 66 | -------------------------------------------------------------------------------- /base/UTC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ComboStrikeHQ/docker-rails/6cbd1f229455086288f6e49065da708391520940/base/UTC -------------------------------------------------------------------------------- /base/appserver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /home/app/webapp 4 | 5 | /opt/wait-for-syslog.sh 6 | exec chpst -u app bundle exec puma -C /etc/puma.rb \ 7 | 2>&1 |logger -t appserver 8 | -------------------------------------------------------------------------------- /base/apt.conf: -------------------------------------------------------------------------------- 1 | APT::Get::Install-Recommends "false"; 2 | 3 | Dpkg::Options { 4 | "--force-confdef"; 5 | "--force-confold"; 6 | } 7 | -------------------------------------------------------------------------------- /base/bundle_config: -------------------------------------------------------------------------------- 1 | --- 2 | BUNDLE_DEPLOYMENT: "true" 3 | BUNDLE_WITHOUT: "development:test" 4 | BUNDLE_PATH: "vendor/bundle" 5 | -------------------------------------------------------------------------------- /base/clockwork.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Directly exit - do not raise and report a SignalException 4 | Signal.trap('TERM') { exit } 5 | 6 | load 'config/clockwork.rb' 7 | 8 | Clockwork.run 9 | -------------------------------------------------------------------------------- /base/clockwork.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /home/app/webapp 4 | 5 | bundle show clockwork || sleep infinity 6 | 7 | /opt/wait-for-syslog.sh 8 | exec chpst -u app bundle exec rails runner /etc/clockwork.rb \ 9 | 2>&1 |logger -t clockwork 10 | -------------------------------------------------------------------------------- /base/custom-services.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | for fname in /home/app/webapp/bin/services/*; do 4 | [ -f "$fname" ] || continue 5 | service=$(basename "${fname%.*}") 6 | mkdir "/etc/service/$service" 7 | 8 | cat < "/etc/service/$service/run" 9 | #!/bin/bash 10 | 11 | cd /home/app/webapp 12 | 13 | /opt/wait-for-syslog.sh 14 | exec chpst -u app bundle exec "$fname" \ 15 | 2>&1 |logger -t "$service" 16 | STR 17 | chmod +x "/etc/service/$service/run" 18 | done 19 | -------------------------------------------------------------------------------- /base/db_migrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd /home/app/webapp 4 | 5 | if bundle show rails_migrate_mutex; then 6 | chpst -u app bundle exec rake db:migrate:mutex 7 | fi 8 | -------------------------------------------------------------------------------- /base/google-chrome.list: -------------------------------------------------------------------------------- 1 | deb http://dl.google.com/linux/chrome/deb/ stable main 2 | -------------------------------------------------------------------------------- /base/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | workers ENV.fetch('PUMA_WORKERS', `nproc`).to_i 4 | 5 | max_threads = ENV.fetch('PUMA_MAX_THREADS', 16).to_i 6 | min_threads = ENV.fetch('PUMA_MIN_THREADS', max_threads).to_i 7 | threads min_threads, max_threads 8 | 9 | bind 'tcp://0.0.0.0:8080' 10 | preload_app! 11 | 12 | # Don't wait for workers to finish their work. We might have long-running HTTP requests. 13 | # But docker gives us only 10 seconds to gracefully handle our shutdown process. 14 | # This settings tries to shut down all threads after 2 seconds. Puma then gives each thread 15 | # an additional 5 seconds to finish the work. This adds up to 7 seconds which is still below 16 | # docker's maximum of 10 seconds. 17 | # This setting only works on Puma >= 3.4.0. 18 | force_shutdown_after 2 if respond_to?(:force_shutdown_after) 19 | 20 | on_worker_boot do 21 | ActiveRecord::Base.establish_connection if defined?(ActiveRecord) 22 | end 23 | 24 | # As we are preloading our application and using ActiveRecord 25 | # it's recommended that we close any connections to the database here to prevent connection leakage 26 | # This rule also applies to any connections to external services (Redis, databases, memcache, ...) 27 | # that might be started automatically by the framework. 28 | before_fork do 29 | ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) 30 | end 31 | 32 | custom_config = '/home/app/webapp/config/puma.rb' 33 | instance_eval(File.read(custom_config)) if File.exist?(custom_config) 34 | -------------------------------------------------------------------------------- /base/rails-assets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if ! bundle show sprockets; then 4 | echo "No sprockets found - not compiling any assets." 5 | exit 0 6 | fi 7 | 8 | if bundle show pg; then 9 | export DATABASE_URL="postgres://noop" 10 | elif bundle show mysql2; then 11 | export DATABASE_URL="mysql2://noop" 12 | elif bundle show sqlite3; then 13 | export DATABASE_URL="sqlite3://noop" 14 | else 15 | echo "Cannot find database gem - asset compilation might fail." 16 | fi 17 | 18 | if [ -e .env ]; then 19 | source .env 20 | fi 21 | 22 | bundle exec rake assets:precompile SECRET_KEY_BASE=noop 23 | -------------------------------------------------------------------------------- /base/sidekiq.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SIDEKIQ_THREADS=${SIDEKIQ_THREADS:-16} 4 | 5 | cd /home/app/webapp 6 | 7 | bundle show sidekiq || sleep infinity 8 | 9 | SIDEKIQ_CONFIG="/home/app/webapp/config/sidekiq.yml" 10 | if [ ! -f $SIDEKIQ_CONFIG ]; then 11 | SIDEKIQ_CONFIG="/etc/sidekiq.yml" 12 | fi 13 | 14 | /opt/wait-for-syslog.sh 15 | exec chpst -u app bundle exec sidekiq -e $RAILS_ENV -t 5 -c $SIDEKIQ_THREADS -C $SIDEKIQ_CONFIG \ 16 | 2>&1 |logger -t sidekiq 17 | -------------------------------------------------------------------------------- /base/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :queues: 3 | - default 4 | - mailers 5 | -------------------------------------------------------------------------------- /base/wait-for-syslog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # syslog-ng takes a while to boot up. Once it's running, it prints a startup message 4 | # to /var/syslog. Let's wait for that message to appear before we start logging anything. 5 | while [ ! -s /var/log/syslog ]; do 6 | sleep 0.3 7 | done 8 | -------------------------------------------------------------------------------- /base/yarn.list: -------------------------------------------------------------------------------- 1 | deb https://dl.yarnpkg.com/debian/ stable main 2 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Build and squash baseimage 4 | docker build -t docker-rails-base ./base 5 | ID=$(docker run -d docker-rails-base true) 6 | docker export $ID | docker import - docker-rails-base-squashed 7 | 8 | # Create onbuild image 9 | docker build -t docker-rails-onbuild ./onbuild 10 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | . VERSION 4 | 5 | docker login -u $DOCKER_USER -p $DOCKER_PASSWORD 6 | docker tag docker-rails-onbuild $DOCKER_TAG:latest 7 | docker push $DOCKER_TAG:latest 8 | docker tag docker-rails-onbuild $DOCKER_TAG:ruby-2.7 9 | docker push $DOCKER_TAG:ruby-2.7 10 | 11 | if ! docker pull $DOCKER_TAG:$VERSION; then 12 | echo "Releasing new version: $VERSION" 13 | 14 | docker tag docker-rails-onbuild $DOCKER_TAG:$VERSION 15 | docker push $DOCKER_TAG:$VERSION 16 | else 17 | echo "Not overwriting existing version: $VERSION" 18 | fi 19 | -------------------------------------------------------------------------------- /onbuild/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker-rails-base-squashed 2 | 3 | ENV RACK_ENV=production RAILS_ENV=production RAILS_LOG_TO_STDOUT=1 \ 4 | RAILS_SERVE_STATIC_FILES=1 5 | 6 | # Workaround for $HOME being ignored in phusion/baseimage. 7 | # See: https://github.com/phusion/baseimage-docker/issues/119#issuecomment-287970522 8 | RUN echo /home/app > /etc/container_environment/HOME 9 | 10 | CMD ["/sbin/my_init"] 11 | EXPOSE 8080 12 | WORKDIR /home/app/webapp 13 | 14 | ONBUILD COPY Gemfile Gemfile.lock /home/app/webapp/ 15 | ONBUILD RUN gem install bundler -v "$(grep -A 1 'BUNDLED WITH' Gemfile.lock | grep -Po '[\d.]+')" --force 16 | ONBUILD COPY . /home/app/webapp/ 17 | ONBUILD RUN chown -R app:app . 18 | 19 | ONBUILD USER app 20 | 21 | ONBUILD RUN bundle install --jobs 4 22 | ONBUILD ARG BUGSNAG_API_KEY 23 | ONBUILD ARG BUGSNAG_APP_VERSION 24 | ONBUILD RUN mkdir -p db public/assets log tmp vendor 25 | ONBUILD RUN /opt/rails-assets.sh 26 | 27 | ONBUILD USER root 28 | 29 | ONBUILD RUN find vendor -name *.gem -delete 30 | ONBUILD RUN /opt/custom-services.sh 31 | 32 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | if [ "$1" == "" ]; then 4 | echo "Usage $0 " 5 | exit 1 6 | fi 7 | 8 | git checkout master 9 | if [ "$(git diff master)" != "" ]; then 10 | echo "Make sure your working tree is clean and you are on the master branch." 11 | exit 1 12 | fi 13 | 14 | echo "Releasing version $1" 15 | 16 | echo "VERSION=$1" > VERSION 17 | $EDITOR CHANGELOG.md 18 | 19 | git add VERSION CHANGELOG.md 20 | git commit -m "Release version $1" 21 | git tag v$1 master 22 | 23 | git show 24 | echo "Are you sure you want to push? Press ENTER" 25 | read 26 | 27 | git push origin master 28 | git push origin v$1 29 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | function testapp_run() { 4 | if ! docker-compose run app "$@"; then 5 | echo "Test '$@' failed!" 6 | exit 1 7 | fi 8 | } 9 | 10 | function check_logs { 11 | local n=1 12 | local max=10 13 | local delay=15 14 | while true; do 15 | docker-compose logs app | grep "$1" | grep "$2" && break || { 16 | if [[ $n -lt $max ]]; then 17 | ((n++)) 18 | echo "Check failed. Attempt $n/$max:" 19 | sleep $delay; 20 | else 21 | echo "Couldn't find $1.*$2 in the logs after $n attempts." 22 | exit 1 23 | fi 24 | } 25 | done 26 | } 27 | 28 | cd test 29 | rm -rf testapp 30 | 31 | # Set up testing rails app 32 | gem install bundler 33 | gem install rails -v '<7' 34 | rails new testapp -d postgresql -m template.rb --skip-bundle 35 | cp -r files/* testapp 36 | chmod 644 testapp/config/master.key 37 | ( 38 | cd testapp 39 | bundle config set cache_all true 40 | bundle package 41 | rails webpacker:install 42 | ) 43 | 44 | # Build container 45 | docker-compose kill || true 46 | docker-compose rm -f 47 | docker-compose build 48 | docker-compose up -d app redis postgres 49 | 50 | # Check that app server boots correctly, ENV variables are exposed and sidekiq works properly 51 | RESULT=$(docker-compose run curl -s --retry 60 --retry-delay 1 --retry-connrefused http://app:8080/) 52 | [[ "$RESULT" =~ 'ok' ]] || exit 1 53 | 54 | # Check that static file serving is enabled 55 | docker-compose run curl -v \ 56 | -4 \ 57 | --retry 60 \ 58 | --retry-delay 1 \ 59 | --retry-connrefused http://app:8080/robots.txt \ 60 | | grep documentation || exit 1 61 | 62 | # Check that logs are sent to STDOUT 63 | check_logs appserver "Completed 200 OK" || exit 1 64 | check_logs sidekiq "ClockworkTestWorker" || exit 1 65 | check_logs clockwork "Triggering" || exit 1 66 | check_logs test_service "Running" || exit 1 67 | 68 | # Clean up 69 | docker-compose stop 70 | 71 | # Check that imagemagick is present 72 | testapp_run which convert 73 | 74 | # Check that ruby uses jemalloc 75 | testapp_run bash -c "ldd /usr/local/bin/ruby |grep jemalloc" 76 | 77 | # Check that openssl/readline is working in ruby 78 | testapp_run ruby -r readline -e puts 79 | testapp_run ruby -r openssl -e puts 80 | 81 | # Check that nodejs, npm and bower are present 82 | RESULT=$(testapp_run node -p '1+1') 83 | [[ "$RESULT" == "2"* ]] || exit 1 84 | testapp_run npm -v 85 | testapp_run bower -v 86 | 87 | # Check that asset gzipping works 88 | FILE="/home/app/webapp/public/packs/js/application-*.js" 89 | testapp_run bash -ec "gzip -dc < $FILE.gz | diff - $FILE" 90 | 91 | echo "Tests OK" 92 | -------------------------------------------------------------------------------- /test/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | app: 4 | build: testapp 5 | environment: 6 | - DATABASE_URL=postgresql://postgres@postgres/postgres 7 | - REDIS_URL=redis://redis 8 | - SECRET_KEY_BASE=noop 9 | - TEST_ENV=hey 10 | - NODE_DISABLE_COLORS=1 11 | ports: 12 | - '8080:8080' 13 | links: 14 | - redis 15 | - postgres 16 | redis: 17 | image: redis:4 18 | postgres: 19 | image: postgres:9.6 20 | environment: 21 | - POSTGRES_HOST_AUTH_METHOD=trust 22 | curl: 23 | image: appropriate/curl 24 | -------------------------------------------------------------------------------- /test/files/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker-rails-onbuild 2 | 3 | # wait for postgres db in docker-compose to fully boot up 4 | COPY wait_for_postgres.sh /etc/my_init.d/00_wait_for_postgres.sh 5 | -------------------------------------------------------------------------------- /test/files/app/controllers/test_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TestController < ApplicationController 4 | def index 5 | Post.create! 6 | 7 | # The worker runs `Post.create!`. Wait for it to finish. 8 | TestWorker.perform_async 9 | sleep 2 10 | 11 | render plain: message 12 | rescue StandardError => error 13 | render plain: error 14 | end 15 | 16 | private 17 | 18 | def message 19 | return 'env_failed' unless ENV['TEST_ENV'] == 'hey' 20 | return 'post_count_failed' unless Post.count == 2 21 | return 'custom_puma_config_failed' unless Redis.current.get('on_worker_boot') == '1' 22 | return 'clockwork_failed' unless Redis.current.get('clockwork_test') == '1' 23 | 24 | 'ok' 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/files/app/models/post.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Post < ActiveRecord::Base 4 | end 5 | -------------------------------------------------------------------------------- /test/files/app/worker/clockwork_test_worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ClockworkTestWorker 4 | include Sidekiq::Worker 5 | 6 | def perform 7 | Redis.current.set('clockwork_test', '1') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/files/app/worker/test_worker.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TestWorker 4 | include Sidekiq::Worker 5 | # put in non-default queue to test default sidekiq.yml 6 | sidekiq_options queue: :mailers 7 | 8 | def perform 9 | Post.create! 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/files/bin/services/test_service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | puts 'Running' 5 | STDOUT.flush 6 | sleep 7 | -------------------------------------------------------------------------------- /test/files/config/clockwork.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Clockwork 4 | every(1.second, 'clockwork_test') { ClockworkTestWorker.perform_async } 5 | end 6 | -------------------------------------------------------------------------------- /test/files/config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | on_worker_boot do 4 | Redis.current.set('on_worker_boot', '1') 5 | end 6 | -------------------------------------------------------------------------------- /test/files/db/migrate/1_create_posts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreatePosts < ActiveRecord::Migration[5.1] 4 | def change 5 | create_table :posts 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/files/vendor/vendor-gem/vendor-gem.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = 'vendor-gem' 5 | spec.version = '1.0.0' 6 | spec.authors = ['Foo Bar'] 7 | spec.email = ['foo@example.com'] 8 | spec.summary = 'Summary' 9 | end 10 | -------------------------------------------------------------------------------- /test/files/wait_for_postgres.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | sleep 10 3 | -------------------------------------------------------------------------------- /test/template.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Components we want to test 4 | gem 'rails_migrate_mutex' 5 | gem 'rack-timeout' 6 | gem 'sidekiq' 7 | gem 'clockwork' 8 | gem 'rake' 9 | 10 | # Ensure that gems with C lib dependencies work 11 | gem 'sqlite3' 12 | gem 'mysql2' 13 | gem 'nokogiri' 14 | gem 'sassc' 15 | gem 'google-protobuf' 16 | gem 'ox' 17 | gem 'oj' 18 | 19 | # Ensure that git works 20 | gem 'currencies', git: 'https://github.com/hexorx/currencies.git', tag: 'v0.4.3' 21 | 22 | # Ensure that vendored gems work 23 | gem 'vendor-gem', path: './vendor/vendor-gem' 24 | 25 | # Test action 26 | route 'root "test#index"' 27 | --------------------------------------------------------------------------------