ruby on rails and docker - why should i care?
TRANSCRIPT
Ruby on Rails& Docker
Why should I care?
Adam Hodowany
About me
• Adam Hodowany
• Ruby and JavaScript developer in Monterail
• @adhodak
Docker LessDevOps Developer
story
I wrote an app!Now what?
$ bundle exec rails server -p 80
It is a joke. Please don’t do it.
Duh
But
($7 + $9) ÷ month
> UserAnswer.count
=> 158_565
http://giphy.com/gifs/ohdY5OaQmUmVW
VPS it is
Repeatability
Infrastructure as Code
Vagrant + Ansible
Vagrant
• “Development environments made easy”
• Virtual Machine without overhead
• Free, local VPS for testing provisioning scripts
• Local staging
# Vagrantfile
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.network "forwarded_port", guest: 80, host: 8080
config.vm.network "forwarded_port", guest: 3000, host: 3030
end
$ vagrant up
# break things
$ vagrant destroy
$ vagrant up
Ansible
• Automation tool
• Configuration using .YML files
• No client on the server
# build-server.yml
---
- hosts: all
remote_user: "{{ deploy_user }}"
roles:
### Server provisioning roles:
- { role: linux-prereqs }
- { role: postgresql }
- { role: rbenv }
- { role: ruby }
- { role: nginx }
### Site specific roles:
- { role: prepare_site }
- { role: copy-env }
- create database- copy database.yml- copy nginx config
Docker
Docker:your application in a
container
Image: KMJ at the German language Wikipedia
Container1. Application
2. All dependencies
3. Common interface
4. ???
5. PROFIT
$ bundle exec rails server
$ php artisan serve
$ python manage.py runserver
$ docker run docker-image
How is it different from VM?
Virtual Machine: Docker:
https://www.docker.com/what-docker
# Dockerfile
FROM ruby:2.2.0
RUN apt-get update -qq && apt-get install -y build-essential nodejs && gem install bundler
ENV APP_HOME /myapp
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile $APP_HOME/Gemfile
ADD Gemfile.lock $APP_HOME/Gemfile.lock
RUN bundle install
ADD . $APP_HOME
CMD ["bundle", "exec", "rails", "server", "--binding", "0.0.0.0"]
- Base Image
- Copy whole application to the image
|- Default “run” command
$ docker build -t my-project .
$ docker run -p 3000:3000 my-projecthost:container
Linking containers$ docker run -d --name db postgres$ docker run --link db:db my-project
# config/database.ymldevelopment: host: db ...
Volumes
$ docker run -v /home/hodak/data:/data my-project
or: shared directories(or files)
(but most likely directories)
host:container
Docker repositories• Docker Hub
• Official
• Free public repositories
• Alternatives exist
• Self-hosted
• Installed with… Docker
or: GitHubs for Docker images
99 official repositories including:nginx, redis, node, postgres,
elasticsearch, wordpress,jenkins, ruby…
Maintained by projects creators
Docker in Development
Docker Machine
Volumes are great• My misconception: code always either in
container, or always out of it.
# DockerfileADD . app_dir/
Makes sure app directoryis always in built image
Enables code reload for development$ docker run -v $(pwd):/app_dir my-project
Docker Compose$ docker build -t my-project .
$ docker run -d --name db postgres$ docker run --link db:db -p "3000:3000" my-project
$ docker run -d --name db postgres$ docker run --link db:db -p "3000:3000" --env-file .env my-project
$ docker run -d --name db postgres$ docker run --link db:db -p "3000:3000" --env-file .env -v /home/hodak/code/my-project:/my-project my-project
$ docker run -d --name db postgres$ docker run -d --name redis redis$ docker run -d --name sidekiq --link redis:redis --link db:db --env-file .env my-project bundle exec sidekiq$ docker run --link db:db --link redis:redis -p "3000:3000" --env-file .env -v /home/hodak/code/my-project:/my-project my-project
# docker-compose.ymldb: image: postgresweb: build: . command: bundle exec rails server --binding 0.0.0.0 ports: - "3000:3000" links: - db volumes: - .:/myapp env_file: .env
$ docker-compose build$ docker-compose up
# docker-compose.ymldb: image: postgresredis: image: redissidekiq: build: . command: bundle exec sidekiq volumes: - .:/myapp links: - db - redis env_file: .envweb: build: . command: bundle exec rails server --binding 0.0.0.0 ports: - "3000:3000" links: - db - redis volumes: - .:/myapp env_file: .env
Use Cases
• Ruby on Rails agency - meh?
• Microservices
• Especially in different technologies
• Working with Freelancers
Admeshgithub.com/admesh/admesh
$ ./configure$ make$ make install
# DockerfileCOPY ./lib/admesh-0.98.2.tar.gz $APP_HOME/admesh.tar.gzRUN tar -zxvf admesh.tar.gz && \ cd $APP_HOME/admesh-0.98.2 && \ ./configure && \ make && \ make install
Continuous Integration with
Docker
Typical Rails-CI setup$ cp config/codeship.database.yml config/database.yml
$ rvm use 2.1.1
$ bundle install
$ export RAILS_ENV=test
$ bundle exec rake db:drop db:create db:structure:load
$ bundle exec rspec
Development CI
Ruby versionBundler version
Ruby versionBundler version
Development CI
Ruby versionBundler version
Ruby versionBundler version
Staging Production
Ruby versionBundler version
Ruby versionBundler version
• More tricky dependencies
• ImageMagick
• ADMesh
• …
Development CI Docker Repository
Dev Build
Production
Build
Staging Production
• Image that has proven itself
• No errors
• Specs passed
$ cp config/codeship.database.yml config/database.yml
$ rvm use 2.1.1
$ bundle install
$ export RAILS_ENV=test
$ bundle exec rake db:drop db:create db:structure:load
$ bundle exec rspec
- Start Postgres
|- Build production image
- Set up database
- Run specsAfter specs pass:
Send imageto Docker Hub
# origin/master -> master$ BRANCH_NAME=`echo $GIT_BRANCH | cut -d '/' -f 2-`$ docker rm --force $(docker ps -aq) || echo "ok"
$ docker run -d --name db postgres$ docker build -t my-project -f Dockerfile.prod .
$ docker run --link db:db -e RAILS_ENV=test my-project bundle exec rake db:create db:migrate$ docker run --link db:db -e RAILS_ENV=test my-project bundle exec rspec
$ docker tag my-project hodak/my-project:$BRANCH_NAME$ docker push hodak/my-project:$BRANCH_NAME
Docker in production
# build-server.yml---- hosts: all remote_user: "{{ deploy_user }}" roles: ### Server provisioning roles: - { role: linux-prereqs } - { role: postgresql } - { role: rbenv } - { role: ruby } - { role: nginx } ### Site specific roles: - { role: prepare_site } - { role: copy-env }
# build-server.yml---- hosts: all remote_user: "{{ deploy_user }}" roles: ### Server provisioning roles: - { role: linux-prereqs } - { role: nginx } - { role: docker } ### Site specific roles: - { role: prepare_site } - { role: copy-env }
- create database- copy database.yml- copy nginx config
• Still must configure nginx
• Pull image from Docker repository
• Not touching GitHub at all
• Synchronise starting containers
• Monitoring
Data-only container pattern
$ docker run --name dbdata postgres echo "Data-only container for postgres"$ docker run -d --volumes-from dbdata --name db postgres
docker-compose?
Just a script
#!/bin/bashdocker run --name dbdata postgres echo "Data-only container for postgres" || echo "already started"docker run -d --volumes-from dbdata --name db --restart=always postgres || docker start dbdocker pull hodak/my-project:masterdocker tag hodak/my-project:master my-projectdocker run --link db:db my-project bundle exec rake db:create db:migratedocker rm --force my-project || echo "not yet created"docker run -d --link db:db --name my-project --restart=always --env-file .env -p "8080:8080" my-project
Ansible
tasks:
- name: application container
docker:
name: my-project
image: hodak/my-project:master
state: reloaded
pull: always
links:
- "db:db"
ports:
- "8080:8080"
restart_policy: always
Custom deploy script for
Capistrano
upstart / systemd / supervisor / monit
Is it worth it?