├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── dckerize.gemspec ├── exe └── dckerize ├── lib ├── dckerize.rb └── dckerize │ ├── generator.rb │ ├── runner.rb │ └── version.rb ├── spec ├── dckerize_spec.rb ├── generator_spec.rb ├── runner_spec.rb └── spec_helper.rb └── templates ├── Dockerfile.erb ├── docker-compose.yml.erb ├── rails-env.conf.erb ├── wait-for-postgres.sh.erb └── webapp.conf.erb /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.4.3 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in dckerize.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pablo Acuña 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dckerize 2 | [![Gem Version](https://badge.fury.io/rb/dckerize.svg)](http://badge.fury.io/rb/dckerize) 3 | [![Build Status](https://travis-ci.org/pacuna/dckerize.svg?branch=master)](https://travis-ci.org/pacuna/dckerize) 4 | 5 | Supercharged Rails development using Docker 6 | 7 | ## Description 8 | 9 | This gem gives you a good starting point to containerize your Rails 5 applications using Docker. 10 | 11 | You'll get 12 | 13 | - An nginx/passenger container environment for your application and all the necessary configurations. It also mounts the application into the container so you can make development changes and not having to rebuild the image. 14 | - A separate container running PostgreSQL 15 | - A separate container for keeping your data using the data-only container pattern. 16 | 17 | ## Requirements 18 | 19 | - Docker >= 1.13 20 | - Docker Compose >= 1.13 21 | 22 | ## Installation 23 | 24 | $ gem install dckerize 25 | 26 | ## Usage 27 | 28 | ### Quickstart 29 | 30 | $ rails new myapp --database=postgresql 31 | $ cd myapp 32 | $ dckerize up myapp 33 | 34 | Configure your database credentials (you can check these in your docker compose file): 35 | 36 | username: myapp 37 | password: mysecretpassword 38 | host: myapp-db 39 | 40 | Dckerize will use the name of your application to create the database host and user names. 41 | It also will create the development database by default (myapp_development in this case). 42 | 43 | Once you have your database configured, you can run: 44 | 45 | ``` 46 | $ docker-compose build 47 | $ docker-compose up 48 | ``` 49 | 50 | And that's it. Now you can go to localhost and see your dockerized Rails application. 51 | 52 | ## Contributing 53 | 54 | 1. Fork it ( https://github.com/pacuna/dckerize/fork ) 55 | 2. Create your feature branch (`git checkout -b my-new-feature`) 56 | 3. Commit your changes (`git commit -am 'Add some feature'`) 57 | 4. Push to the branch (`git push origin my-new-feature`) 58 | 5. Create a new Pull Request 59 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | 8 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "dckerize" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /dckerize.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'dckerize/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "dckerize" 8 | spec.version = Dckerize::VERSION 9 | spec.authors = ["Pablo Acuña"] 10 | spec.email = ["pabloacuna88@gmail.com"] 11 | 12 | spec.summary = %q{Supercharged Rails development using Docker containers.} 13 | spec.homepage = "https://github.com/pacuna/dckerize" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 17 | spec.bindir = "exe" 18 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 19 | spec.require_paths = ["lib", "templates"] 20 | 21 | spec.add_development_dependency "bundler", "~> 1.7" 22 | spec.add_development_dependency "rake", "~> 10.0" 23 | spec.add_development_dependency 'rspec' 24 | 25 | end 26 | -------------------------------------------------------------------------------- /exe/dckerize: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "dckerize" 4 | 5 | begin 6 | Dckerize::Runner.new(ARGV).execute 7 | rescue Exception => e 8 | puts e.message 9 | exit 0 10 | end 11 | -------------------------------------------------------------------------------- /lib/dckerize.rb: -------------------------------------------------------------------------------- 1 | require "dckerize/version" 2 | require "dckerize/generator" 3 | require "dckerize/runner" 4 | require 'erb' 5 | require 'fileutils' 6 | 7 | module Dckerize 8 | def self.root 9 | File.dirname __dir__ 10 | end 11 | 12 | def self.templates 13 | File.join root, 'templates' 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /lib/dckerize/generator.rb: -------------------------------------------------------------------------------- 1 | module Dckerize 2 | class Generator 3 | attr_accessor :name 4 | def initialize(name) 5 | @name = name 6 | end 7 | 8 | def get_binding 9 | binding 10 | end 11 | 12 | def templates 13 | Dckerize.templates 14 | end 15 | 16 | def up 17 | 18 | raise Dckerize::Runner::DOCKERFILE_EXISTS if File.exists?('Dockerfile.development') 19 | raise Dckerize::Runner::DOCKERCOMPOSE_EXISTS if File.exists?('docker-compose.yml') 20 | 21 | create_from_template('Dockerfile.erb', 'Dockerfile.development') 22 | create_from_template('webapp.conf.erb', "webapp.conf") 23 | create_from_template('wait-for-postgres.sh.erb', "wait-for-postgres.sh") 24 | create_from_template('rails-env.conf.erb', "rails-env.conf") 25 | create_from_template('docker-compose.yml.erb', "docker-compose.yml") 26 | end 27 | 28 | private 29 | def create_from_template(template_name, output_file) 30 | template = ERB.new(File.read("#{templates}/#{template_name}"), nil, '-') 31 | result = template.result(binding) 32 | File.open("#{output_file}", 'w') { |file| file.write(result) } 33 | 34 | # add execution permissions for setup.sh 35 | system "chmod +x #{output_file}" if output_file == 'wait-for-postgres.sh' 36 | end 37 | 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /lib/dckerize/runner.rb: -------------------------------------------------------------------------------- 1 | module Dckerize 2 | class Runner 3 | 4 | ERROR_MESSAGE = 'USAGE: dckerize up APP_NAME' 5 | CONF_FOLDER_EXISTS = 'ERROR: conf folder already exists.' 6 | DOCKERFILE_EXISTS = 'ERROR: Dockerfile.development already exists.' 7 | DOCKERCOMPOSE_EXISTS = 'ERROR: docker-compose already exists.' 8 | def initialize(options) 9 | @options = options 10 | end 11 | 12 | def execute 13 | raise ERROR_MESSAGE unless valid? 14 | Dckerize::Generator.new(@options[1]).up 15 | end 16 | 17 | def valid? 18 | # first option should always be up 19 | return false if @options[0] != 'up' 20 | true 21 | end 22 | end 23 | 24 | end 25 | -------------------------------------------------------------------------------- /lib/dckerize/version.rb: -------------------------------------------------------------------------------- 1 | module Dckerize 2 | VERSION = "0.9.2" 3 | end 4 | -------------------------------------------------------------------------------- /spec/dckerize_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Dckerize do 4 | it 'has a version number' do 5 | expect(Dckerize::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe Dckerize::Generator do 3 | let (:generator) { 4 | Dckerize::Generator.new('myapp') 5 | } 6 | 7 | describe '#initialize' do 8 | it 'should be possible to initialize it with a name and a db' do 9 | expect(generator.name).to eq('myapp') 10 | end 11 | end 12 | 13 | context 'file already present' do 14 | 15 | describe 'Dockerfile.development already exitst' do 16 | before(:each) do 17 | FileUtils.touch('Dockerfile.development') 18 | end 19 | 20 | after(:each) do 21 | clean_files 22 | end 23 | 24 | it 'should not create the Dockerfile.development and give notification' do 25 | expect{ generator.up }.to raise_error Dckerize::Runner::DOCKERFILE_EXISTS 26 | end 27 | end 28 | 29 | describe 'docker-compose already exists' do 30 | before(:each) do 31 | FileUtils.touch('docker-compose.yml') 32 | end 33 | 34 | after(:each) do 35 | clean_files 36 | end 37 | 38 | it 'should not create the docker-compose file and give notification' do 39 | expect{ generator.up }.to raise_error Dckerize::Runner::DOCKERCOMPOSE_EXISTS 40 | end 41 | end 42 | end 43 | 44 | 45 | context 'generator with postgres' do 46 | describe '#up' do 47 | 48 | before(:each) do 49 | generator.up 50 | end 51 | 52 | after(:each) do 53 | clean_files 54 | end 55 | 56 | it 'should generate a Dockerfile.development' do 57 | expect(File).to exist('Dockerfile.development') 58 | end 59 | 60 | it 'should generate a site config for nginx' do 61 | expect(File).to exist("webapp.conf") 62 | end 63 | 64 | context 'describin docker-compose.yml file' do 65 | it 'should generate a data image for postgres' do 66 | expect(File.read('docker-compose.yml')) 67 | .to include('image: postgres') 68 | end 69 | 70 | it 'should generate a service named postgres' do 71 | expect(File.read('docker-compose.yml')) 72 | .to include('postgres:') 73 | end 74 | 75 | it 'should generate a db password entry environment' do 76 | expect(File.read('docker-compose.yml')) 77 | .to include("POSTGRES_PASSWORD=mysecretpassword") 78 | end 79 | 80 | it 'should generate a correct service for data-only container' do 81 | expect(File.read('docker-compose.yml')) 82 | .to include("/var/lib/postgresql") 83 | end 84 | end 85 | 86 | it 'should generate a docker-compose file' do 87 | expect(File).to exist("docker-compose.yml") 88 | end 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /spec/runner_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | describe Dckerize::Runner do 3 | 4 | let(:runner) { 5 | Dckerize::Runner.new(['up', 'myapp']) 6 | } 7 | 8 | let(:invalid_runner_without_up) { 9 | Dckerize::Runner.new(['something', 'myapp']) 10 | } 11 | 12 | after(:each) do 13 | clean_files 14 | end 15 | 16 | describe '#initialize' do 17 | context 'valid runner' do 18 | it 'should initialize correctly' do 19 | expect(runner).to be_valid 20 | end 21 | end 22 | 23 | context 'invalid runner' do 24 | it 'should be invalid without up' do 25 | expect(invalid_runner_without_up).to_not be_valid 26 | end 27 | 28 | end 29 | end 30 | 31 | describe '#execute' do 32 | context 'valid runner' do 33 | it 'should not raise error' do 34 | expect{ runner.execute }.to_not raise_error 35 | end 36 | end 37 | 38 | context 'invalid runner' do 39 | it 'should raise error without up as first option' do 40 | expect{ invalid_runner_without_up.execute }.to raise_error Dckerize::Runner::ERROR_MESSAGE 41 | end 42 | 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | 3 | require 'dckerize' 4 | 5 | def clean_files 6 | FileUtils.rm_f('Dockerfile.development') 7 | FileUtils.rm_f('docker-compose.yml') 8 | FileUtils.rm_f('setup.sh') 9 | FileUtils.rm_f('webapp.conf') 10 | FileUtils.rm_f('rails-env.conf') 11 | end 12 | -------------------------------------------------------------------------------- /templates/Dockerfile.erb: -------------------------------------------------------------------------------- 1 | FROM phusion/passenger-ruby25:1.0.1 2 | 3 | # Set correct environment variables. 4 | ENV HOME /root 5 | 6 | # Use baseimage-docker's init process. 7 | CMD ["/sbin/my_init"] 8 | 9 | # Additional packages 10 | RUN apt-get update && apt-get install -y -o Dpkg::Options::="--force-confold" postgresql-client tzdata 11 | 12 | # Enable Nginx and Passenger 13 | RUN rm -f /etc/service/nginx/down 14 | 15 | ENV LANG en_US.UTF-8 16 | ENV LANGUAGE en_US.UTF-8 17 | ENV LC_ALL en_US.UTF-8 18 | 19 | # Install gems: it's better to build an independent layer for the gems 20 | # so they are cached during builds unless Gemfile changes 21 | WORKDIR /tmp 22 | ADD Gemfile /tmp/ 23 | ADD Gemfile.lock /tmp/ 24 | RUN bundle install --jobs 4 --retry 3 25 | 26 | # Add virtual host entry for the application 27 | RUN rm /etc/nginx/sites-enabled/default 28 | ADD webapp.conf /etc/nginx/sites-enabled/webapp.conf 29 | 30 | # In case we need some environmental variables in Nginx 31 | ADD rails-env.conf /etc/nginx/main.d/rails-env.conf 32 | 33 | # Copy application into the container and use right permissions: passenger 34 | # uses the app user for running the application 35 | RUN mkdir /home/app/webapp 36 | COPY --chown=app:app . /home/app/webapp 37 | WORKDIR /home/app/webapp 38 | 39 | # Clean up APT when done. 40 | RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 41 | -------------------------------------------------------------------------------- /templates/docker-compose.yml.erb: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | <%= @name %>-db: 4 | image: postgres:11.1 5 | environment: 6 | - POSTGRES_PASSWORD=mysecretpassword 7 | - POSTGRES_USER=<%= @name %> 8 | - POSTGRES_DB=<%= @name %>_development 9 | volumes: 10 | - data-volume:/var/lib/postgresql 11 | <%= @name %>-migrate: 12 | build: 13 | context: . 14 | dockerfile: Dockerfile.development 15 | depends_on: 16 | - <%= @name %>-db 17 | environment: 18 | - PASSENGER_APP_ENV=development 19 | command: ["./wait-for-postgres.sh", "<%= @name %>-db", "bin/rails", "db:migrate"] 20 | <%= @name %>: 21 | build: 22 | context: . 23 | dockerfile: Dockerfile.development 24 | depends_on: 25 | - <%= @name %>-db 26 | - <%= @name %>-migrate 27 | ports: 28 | - "80:80" 29 | environment: 30 | - PASSENGER_APP_ENV=development 31 | - RAILS_LOG_TO_STDOUT=true 32 | volumes: 33 | - .:/home/app/webapp 34 | volumes: 35 | data-volume: 36 | -------------------------------------------------------------------------------- /templates/rails-env.conf.erb: -------------------------------------------------------------------------------- 1 | # example variables 2 | env RAILS_LOG_TO_STDOUT; 3 | -------------------------------------------------------------------------------- /templates/wait-for-postgres.sh.erb: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | host="$1" 6 | shift 7 | cmd="$@" 8 | 9 | until PGPASSWORD=mysecretpassword psql -h "$host" -U "<%= @name %>" -d "<%= @name %>_development" -c '\l'; do 10 | >&2 echo "Postgres is unavailable - sleeping" 11 | sleep 1 12 | done 13 | 14 | >&2 echo "Postgres is up - executing command" 15 | exec $cmd 16 | 17 | -------------------------------------------------------------------------------- /templates/webapp.conf.erb: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name _; 4 | root /home/app/webapp/public; 5 | 6 | passenger_enabled on; 7 | passenger_user app; 8 | 9 | passenger_ruby /usr/bin/ruby2.5; 10 | } 11 | --------------------------------------------------------------------------------