|
|\_ app
|...
|\_ docker
| |
| |\_ development
| |\_ staging
| \_ production
| |
| |\_ .env
| |\_ app-env.onf
| |\_ app.nginx.conf
| |\_ Dockerfile
| |\_ postgres-env.conf
| \_ rails-env.conf
|
|\_ docker-compose.yml
|\_ docker-compose.staging.yml
\_ docker-compose.production.yml
Your docker/development folder contain Dockerfile for your development. Staging and Production have the same file, but their content will be different. For example, .env file which contain your secret.
This file will be use as an application environment while deploying.
This file should be listed in your gitignore
RAILS_ENV=production
SECRET_KEY_BASE=your_secret_key
Please note that, your SECRET_KEY_BASE
should never ever appear in your repository, I add it here because I have it in
my .gitignore file. This file will later be copied to your remote host.
Application environment.
# /etc/nginx/conf.d/00_app_env.conf
# File will be overwritten if user runs the container with `-e PASSENGER_APP_ENV=...`!
passenger_app_env production;
An Nginx configuration for your server. Please note that your ruby version at the last line must match your Docker file base image ruby version.
# /etc/nginx/sites-enabled/app.nginx.conf:
server {
listen 80;
root /home/app/your_app_name/public;
passenger_enabled on;
passenger_user app;
passenger_ruby /usr/bin/ruby2.3;
}
Base image for your application.
FROM phusion/passenger-ruby23:latest
MAINTAINER Wiwatta Mongkhonchit "[email protected]"
# Set correct environment variables.
ENV HOME /root
# Use baseimage-docker's init process.
CMD ["/sbin/my_init"]
# Expose Nginx HTTP service
EXPOSE 80
# Start Nginx / Passenger
RUN rm -f /etc/service/nginx/down
# Remove the default site
RUN rm /etc/nginx/sites-enabled/default
# Nginx App setup
ADD docker/staging/app.nginx.conf /etc/nginx/sites-enabled/app.nginx.conf
ADD docker/staging/postgres-env.conf /etc/nginx/main.d/postgres-env.conf
ADD docker/staging/rails-env.conf /etc/nginx/main.d/rails-env.conf
ADD docker/staging/app-env.conf /etc/nginx/conf.d/00_app_env.conf
# Update for security reason
RUN apt-get update && apt-get upgrade -y -o Dpkg::Options::="--force-confold"
# Gem caching
WORKDIR /tmp
ADD Gemfile /tmp/
ADD Gemfile.lock /tmp/
RUN bundle install --jobs 20 --retry 5
# App setup
#
# This is your application setup steps, you should also do what you need to do here to make your
# application working for production environment.
#
WORKDIR /home/app/your_app_name
ADD . /home/app/your_app_name
RUN rm -f config/database.yml
RUN mv config/database.yml.sample config/database.yml
RUN chown -R app:app /home/app/your_app_name
RUN rake assets:precompile RAILS_ENV=production
# Clean up APT when done.
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
Please note that, I swap database.yml with database.yml.sample because in my database.yml.sample contain postgres container port resolving which in my development, it's not necessary.
default: &default
adapter: postgresql
encoding: unicode
pool: 5
timeout: 5000
username: postgres
host: postgres
port: <%= ENV['POSTGRES_PORT_5432_TCP_PORT'] %>
development:
<<: *default
database: app_development
test:
<<: *default
database: app_test
production:
<<: *default
database: app_production
Just a postgres environment variable, so that your app can pickup your database.
# /etc/nginx/main.d/postgres-env.conf:
env POSTGRES_PORT_5432_TCP_ADDR;
env POSTGRES_PORT_5432_TCP_PORT;
To set environment variable for your application while running. This related to your .env file.
# /etc/nginx/main.d/rails-env.conf:
env RAILS_ENV;
env SECRET_KEY_BASE;
There are 3 docker-compose files. The normal one, docker-compose.yml is for you development. As for your staging and production, I separated it into 2 file because I would like let Capistrano be able to pickup staging and production compose file which inside contain some different things.
version: "2"
services:
web:
restart: always
build:
context: .
dockerfile: docker/staging/Dockerfile
env_file: docker/staging/.env
# Map your desire staging port here, if you plan to run this in the same host without using
# subdomain.
ports:
- "9876:80"
depends_on:
- postgres
postgres:
image: postgres:9.5
ports:
- "5432"
volumes_from:
- data
data:
image: postgres:9.5
version: "2"
services:
web:
restart: always
build:
context: .
dockerfile: docker/production/Dockerfile
env_file: docker/production/.env
ports:
- "80:80"
depends_on:
- postgres
postgres:
image: postgres:9.5
ports:
- "5432"
volumes_from:
- data
data:
image: postgres:9.5
If you are not familiar with Capistrano please visit their site, then do install it into your project.
After setup Capistrano, here is a steps to take to be able to deploy your app with just one command.
cap production deploy
First, make sure that you setup your remote host for Capistrano to be able to do things, e.g. if you're using DigitalOcean, you should follow Initial Server Setup and create some user you use for deployment. I will call this guy "deploy" for the sake of simplicity.
After "deploy" is able to connect to your server, then let's make some task for it to be able to run with Docker Compose smoothly.
These following files reside in lib/capistrano/tasks folder.
namespace :setup do
desc "Upload .env file to shared folder"
task :env do
on roles(:app) do
upload! fetch(:env_file_path), [shared_path, fetch(:env_file_path)].join('/')
end
end
namespace :check do
desc "Task description"
task :linked_files => fetch(:env_file_path)
end
end
env_file_path is set in config/deploy/production.rb with the path to .env file.
...
set :env_file_path, 'docker/production/.env'
...
desc "Check that we can access everything"
task :check_write_permissions do
on roles(:app) do |host|
if test("[ -w #{fetch(:deploy_to)} ]")
info "#{fetch(:deploy_to)} is writable on #{host}"
else
error "#{fetch(:deploy_to)} is not writable on #{host}"
end
end
end
namespace :composing do
desc "Build application images"
task :build do
on roles(:app) do
within current_path do
execute("docker-compose",
"--project-name=#{fetch(:application)}_#{fetch(:stage)}",
"-f", "docker-compose.#{fetch(:stage)}.yml",
"build"
)
end
end
end
desc "Take down compose application containers"
task :down do
on roles(:app) do
within current_path do
execute("docker-compose",
"--project-name=#{fetch(:application)}_#{fetch(:stage)}",
"-f", "docker-compose.#{fetch(:stage)}.yml",
"down"
)
end
end
end
namespace :restart do
desc "Rebuild and restart web container"
task :web do
on roles(:app) do
within current_path do
execute("docker-compose",
"--project-name=#{fetch(:application)}_#{fetch(:stage)}",
"-f", "docker-compose.#{fetch(:stage)}.yml",
"build", "web"
)
execute("docker-compose",
"--project-name=#{fetch(:application)}_#{fetch(:stage)}",
"-f", "docker-compose.#{fetch(:stage)}.yml",
"up", "-d", "--no-deps", "web"
)
end
end
end
end
namespace :database do
desc "Up database and make sure it's ready"
task :up do
on roles(:app) do
within current_path do
execute("docker-compose",
"--project-name=#{fetch(:application)}_#{fetch(:stage)}",
"-f", "docker-compose.#{fetch(:stage)}.yml",
"up", "-d", "--no-deps", "postgres"
)
end
end
sleep 5
end
desc "Create database"
task :create do
on roles(:app) do
within current_path do
execute("docker-compose",
"--project-name=#{fetch(:application)}_#{fetch(:stage)}",
"-f", "docker-compose.#{fetch(:stage)}.yml",
"run", "--rm", "web", "rake", "db:create"
)
end
end
end
desc "Migrate database"
task :migrate do
on roles(:app) do
within current_path do
execute("docker-compose",
"--project-name=#{fetch(:application)}_#{fetch(:stage)}",
"-f", "docker-compose.#{fetch(:stage)}.yml",
"run", "--rm", "web", "rake", "db:migrate"
)
end
end
end
end
end
In your config/deploy.rb, you should have something like this.
...
...
namespace :deploy do
desc "Initialize application"
task :initialize do
invoke 'composing:build'
invoke 'composing:database:up'
invoke 'composing:database:create'
invoke 'composing:database:migrate'
end
after :published, :restart do
invoke 'composing:restart:web'
invoke 'composing:database:migrate'
end
before :finished, :clear_containers do
on roles(:app) do
execute "docker ps -a -q -f status=exited | xargs -r docker rm -v"
execute "docker images -f dangling=true -q | xargs -r docker rmi -f"
end
end
end
The first time you deploy your application, your must run this command
cap production deploy:initialize
To make your database ready for your application. Then, the next time you deploy your application, just run command
cap production deploy
Done.
- Dockerfile, would be awesome if I can build my own image from scratch, which should be very small.
- Scaling. Found a very nice guide but haven't tried it once.
Thanks to a wonderful guys in Docker, Capistrano and Passenger Docker that they made these tools available for developer to automate things pretty easy. Also many thanks to some guides that I do not remember what it is. Without those guides, I won't be able to make these things complete and easy with just one command.