From 9ad0eb7ba3130dcf3a6f37898aa97a4f579a92c4 Mon Sep 17 00:00:00 2001 From: Misha Merkushin Date: Thu, 24 Oct 2019 17:45:03 +0300 Subject: [PATCH] chore: add Docker for development --- .docker/.psqlrc | 26 ++++++++++++ .pryrc | 4 ++ Dockerfile | 49 +++++++++++++++++++++ Gemfile | 1 + Gemfile.lock | 7 +++ bin/setup | 13 +++--- dip.yml | 69 ++++++++++++++++++++++++++++++ docker-compose.yml | 87 ++++++++++++++++++++++++++++++++++++++ docs/development/dip.md | 75 ++++++++++++++++++++++++++++++++ docs/development/docker.md | 83 ++++++++++++++++++++++++++++++++++++ 10 files changed, 406 insertions(+), 8 deletions(-) create mode 100644 .docker/.psqlrc create mode 100644 .pryrc create mode 100644 Dockerfile create mode 100644 dip.yml create mode 100644 docker-compose.yml create mode 100644 docs/development/dip.md create mode 100644 docs/development/docker.md diff --git a/.docker/.psqlrc b/.docker/.psqlrc new file mode 100644 index 0000000..b8379aa --- /dev/null +++ b/.docker/.psqlrc @@ -0,0 +1,26 @@ +-- Don't display the "helpful" message on startup. +\set QUIET 1 + +-- psql writes to a temporary file before then moving that temporary file on top of the old history file +-- a bind mount of a file only bind mounts the inode, so a rename like this won't ever work +\set HISTFILE /var/log/psql_history/.psql_history + +-- Show how long each query takes to execute +\timing + +-- Use best available output format +\x auto + +-- Verbose error reports +\set VERBOSITY verbose + +-- If a command is run more than once in a row, +-- only store it once in the history +\set HISTCONTROL ignoredups +\set COMP_KEYWORD_CASE upper + +-- By default, NULL displays as an empty space. Is it actually an empty +-- string, or is it null? This makes that distinction visible +\pset null '[NULL]' + +\unset QUIET diff --git a/.pryrc b/.pryrc new file mode 100644 index 0000000..18eee07 --- /dev/null +++ b/.pryrc @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +Pry.config.history.should_save = true +Pry.config.history.file = "#{__dir__}/log/.pry_history" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2b5091e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +ARG RUBY_VERSION +FROM ruby:${RUBY_VERSION}-stretch + +ARG PG_MAJOR +ARG NODE_MAJOR +ARG BUNDLER_VERSION +ARG YARN_VERSION + +# PG +RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \ + && echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' ${PG_MAJOR} > /etc/apt/sources.list.d/pgdg.list + +# Node +RUN curl -sL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - + +# Yarn +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list + +# Other dependencies +RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \ + DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \ + build-essential \ + postgresql-client-${PG_MAJOR} \ + nodejs \ + yarn=${YARN_VERSION}-1 \ + less \ + vim && \ + apt-get clean && \ + rm -rf /var/cache/apt/archives/* && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ + truncate -s 0 /var/log/*log + +# Configure bundler and PATH +ENV LANG=C.UTF-8 \ + GEM_HOME=/bundle \ + BUNDLE_JOBS=4 \ + BUNDLE_RETRY=3 +ENV BUNDLE_PATH $GEM_HOME +ENV BUNDLE_APP_CONFIG=$BUNDLE_PATH \ + BUNDLE_BIN=$BUNDLE_PATH/bin +ENV PATH /app/bin:$BUNDLE_BIN:$PATH + +RUN gem update --system && \ + gem install bundler:${BUNDLER_VERSION} + +RUN mkdir -p /app + +WORKDIR /app diff --git a/Gemfile b/Gemfile index 6866304..cdce5f2 100644 --- a/Gemfile +++ b/Gemfile @@ -30,6 +30,7 @@ gem 'bootsnap', '>= 1.1.0', require: false group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] + gem 'pry-rails' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 06e4358..68490b6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -48,6 +48,7 @@ GEM msgpack (~> 1.0) builder (3.2.3) byebug (11.0.1) + coderay (1.1.2) concurrent-ruby (1.1.5) crass (1.0.4) erubi (1.8.0) @@ -79,6 +80,11 @@ GEM nokogiri (1.10.3) mini_portile2 (~> 2.4.0) pg (1.1.4) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-rails (0.3.9) + pry (>= 0.10.4) puma (3.12.1) rack (2.0.7) rack-proxy (0.6.5) @@ -152,6 +158,7 @@ DEPENDENCIES jbuilder (~> 2.5) listen (>= 3.0.5, < 3.2) pg (>= 0.18, < 2.0) + pry-rails puma (~> 3.11) rails (~> 5.2.3) redis (~> 4.0) diff --git a/bin/setup b/bin/setup index 94fd4d7..4e33f64 100755 --- a/bin/setup +++ b/bin/setup @@ -18,19 +18,16 @@ chdir APP_ROOT do system('bundle check') || system!('bundle install') # Install JavaScript dependencies if using Yarn - # system('bin/yarn') + system!('bin/yarn') - # puts "\n== Copying sample files ==" - # unless File.exist?('config/database.yml') - # cp 'config/database.yml.sample', 'config/database.yml' - # end + puts "\n== Copying sample files ==" + unless File.exist?('config/database.yml') + cp 'config/database.yml.example', 'config/database.yml' + end puts "\n== Preparing database ==" system! 'bin/rails db:setup' puts "\n== Removing old logs and tempfiles ==" system! 'bin/rails log:clear tmp:clear' - - puts "\n== Restarting application server ==" - system! 'bin/rails restart' end diff --git a/dip.yml b/dip.yml new file mode 100644 index 0000000..c232446 --- /dev/null +++ b/dip.yml @@ -0,0 +1,69 @@ +version: '4.1' + +interaction: + bash: + description: Open a Bash shell + service: backend + command: /bin/bash + compose_run_options: [no-deps] + + bundle: + description: Run Bundler commands + service: backend + command: bundle + + yarn: + description: Run Yarn commands + service: backend + command: yarn + + rake: + description: Run Rake commands + service: backend + command: bundle exec rake + + rails: + description: Run Rails commands + service: backend + command: bundle exec rails + subcommands: + s: + description: Run Rails server available at http://localhost:3000 + service: rails + compose: + run_options: [service-ports, use-aliases] + + sidekiq: + description: Run Sidekiq worker + service: backend + command: bundle exec sidekiq + + rspec: + description: Run RSpec commands within test environment + service: backend + environment: + RAILS_ENV: test + command: bundle exec rspec + + rubocop: + description: Lint ruby files + service: backend + command: bundle exec rubocop + compose: + run_options: [no-deps] + + psql: + description: Open a Postgres console + service: postgres + default_args: evil_chat_development + command: psql -h postgres -U postgres + + 'redis-cli': + description: Open a Redis console + service: redis + command: redis-cli -h redis + +provision: + - dip compose down --volumes + - dip compose up -d postgres redis + - dip bash -c "./bin/setup" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..007357b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,87 @@ +version: '3.4' + +services: + app: &app + build: + context: . + dockerfile: ./Dockerfile + args: + RUBY_VERSION: '2.6' + PG_MAJOR: '10' + NODE_MAJOR: '11' + BUNDLER_VERSION: '2.0.2' + YARN_VERSION: '1.19.1' + image: brainwashing-dev:1.0.0 + volumes: + - .:/app:cached + tmpfs: + - /tmp + + backend: &backend + <<: *app + stdin_open: true + tty: true + command: /bin/bash + volumes: + - .:/app:cached + # We store Rails cache and gems in volumes to get speed up on Docker for Mac + - rails_cache:/app/tmp/cache + - bundle:/bundle + environment: + HISTFILE: /app/log/.bash_history + MALLOC_ARENA_MAX: 2 + WEB_CONCURRENCY: 1 + EDITOR: vim + RAILS_ENV: ${RAILS_ENV:-development} + DATABASE_URL: postgres://postgres:postgres@postgres:5432 + REDIS_URL: redis://redis:6379/ + WEBPACKER_DEV_SERVER_HOST: webpacker + DISABLE_SPRING: 1 + depends_on: + - postgres + - redis + + rails: + <<: *backend + command: bundle exec rails server -b 0.0.0.0 + ports: + - '3000:3000' + depends_on: + - postgres + - redis + - webpacker + + webpacker: + <<: *app + command: ./bin/webpack-dev-server + ports: + - '3035:3035' + volumes: + - .:/app:cached + - bundle:/bundle + environment: + NODE_ENV: development + RAILS_ENV: ${RAILS_ENV:-development} + WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 + + postgres: + image: postgres:12 + volumes: + - postgres:/var/lib/postgresql/data + - .docker/.psqlrc:/root/.psqlrc:ro + - ./log:/var/log/psql_history + ports: + - 5432 + + redis: + image: redis:4 + volumes: + - redis:/data + ports: + - 6379 + +volumes: + bundle: + postgres: + redis: + rails_cache: diff --git a/docs/development/dip.md b/docs/development/dip.md new file mode 100644 index 0000000..26d906a --- /dev/null +++ b/docs/development/dip.md @@ -0,0 +1,75 @@ +# Docker on Dip Development Configuration + +We have development [Docker configuration]((./docker.md). + +In addition to it you can also use [`dip`](https://github.com/bibendi/dip) – CLI that gives the "native" interaction with applications configured with Docker Compose. It is for the local development only. In practice, it creates the feeling that you work without containers. + +To install `dip` copy and run the command below: + +```sh +gem install dip +``` + +or + +```sh +brew install bibendi/dip/dip +``` + +## Usage + +```sh +# list available commands +dip ls + +# provision application +dip provision + +# run web app with all debuging capabilities (i.e. `binding.pry`) +dip rails s + +# run rails console +dip rails c + +# run migrations +dip rake db:migrate + +# pass env variables into application +dip VERSION=20100905201547 rake db:migrate:down + +# simply launch bash within app directory +dip bash + +# Additional commands + +# update gems or packages +dip bundle install + +# run psql console +dip psql + +# run redis console +dip redis-cli + +# run tests +dip rspec spec/path/to/single/test.rb:23 + +# shutdown all containers +dip down +``` + +Dip can be easily integrated into ZSH (especially if used `agnostic` theme) or Bash shell. +So, there is allow us using all above commands without `dip` prefix. And it looks like we are not using Docker at all (really that's not true). + +```sh +eval "$(dip console)" + +rails c +rails s +rspec spec/test_spec.rb:23 +psql evilmartians_development +VERSION=20100905201547 rake db:migrate:down +rake routes | grep blog +``` + +But after we get out to somewhere from the project's directory, then all Dip's aliases will be cleared. And if we get back, then Dip's aliases would be restored. diff --git a/docs/development/docker.md b/docs/development/docker.md new file mode 100644 index 0000000..a40aa94 --- /dev/null +++ b/docs/development/docker.md @@ -0,0 +1,83 @@ +# Docker for Development + +## Installation + +You need `docker` and `docker-compose` installed (for MacOS just use [official app](https://docs.docker.com/engine/installation/mac/)). + +## Provisioning + +Run the following commands to prepare your Docker dev env: + +```sh +$ docker-compose build +$ docker-compose run --rm backend bundle install +$ docker-compose run --rm backend bundle exec rake db:setup +``` + +It builds the Docker image, installs Ruby dependencies, creates database, run migrations and seeds. + +You're all set! Now you're ready to code! + +## Commands + +* Running the app: + +```sh +# Running Rails server +$ docker-compose up rails +``` + +* Running tests and other tools + +You can spin a container to run tests: + +```sh +$ RAILS_ENV=test docker-compose run --rm backend bundle exec rspec +``` + +There is a way to run a `bash` session within a container and run commands +from inside: + +```sh +$ docker-compose run --rm backend +root@aef12:/app# +``` + +## Image Versioning + +Every time you change the `Dockerfile` contents, you **must** increase the version number in the `docker-compose` file using the following rules: +- Increase the patch version if a bug has been fixed +- Increase the minor version if a dependency has been added or upgraded (e.g. new Ruby version) +- Increase the major version only if the app stack has changed drastically (e.g. switched from fullstack app to API only, or from Ruby to Haskell) + +## Docker Tips + +**TIP #1**: Docker doesn't cleanup after itself well, so you have to do it manually. + +```sh +# to monitor disk usage +docker system df + +# to stop and clean application related containers +docker-compose down + +# and with volumes (useful to reset databases state) +docker-compose down --volumes + +# or even clean the whole docker system (it'll affect other applications too!) +docker system prune +``` + +**TIP #2**: Use `--rm` flag with `run` command to automatically destroy the +container on exit, e.g. `docker-compose run --rm runner`. + +**TIP #3**: Add system aliases: + +``` +alias dcr='docker-compose run --rm' +alias dcu='docker-compose up' +alias dcs='docker-compose stop' +alias dstats='docker stats --format "table {{.Name}}:\t{{.MemUsage}}\t{{.CPUPerc}}"' +``` + +**TIP #3**: Use [`dip`](./dip.yml).