Merge pull request #4014 from crohr/feature/dockerfile

dockerize
pull/4005/head
Oliver Günther 9 years ago
commit 4ddb0667d2
  1. 7
      .dockerignore
  2. 2
      .travis.yml
  3. 49
      Dockerfile
  4. 13
      Gemfile
  5. 17
      Gemfile.lock
  6. 2
      doc/operation_guides/manual/installation-guide.md
  7. 3
      docker/Procfile
  8. 26
      docker/cron
  9. 211
      docker/nginx.conf.erb
  10. 23
      docker/web
  11. 2
      docker/worker

@ -0,0 +1,7 @@
.git
.bundle
.env*
tmp
frontend/node_modules
# travis
vendor/bundle

@ -39,7 +39,7 @@ cache:
- frontend/node_modules
- frontend/bower_components
bundler_args: --without development production
bundler_args: --without development production docker
branches:
only:

@ -0,0 +1,49 @@
FROM ruby:2.1
ENV NODE_VERSION="0.12.7"
ENV BUNDLER_VERSION="1.10.6"
# install node + npm
RUN curl https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz | tar xzf - -C /usr/local --strip-components=1
# Using /home/app since npm cache and other stuff will be put there when running npm install
# We don't want to pollute any locally-mounted directory
RUN useradd -d /home/app -m app
RUN mkdir -p /usr/src/app
RUN chown -R app /usr/src/app /usr/local/bundle
RUN gem install bundler --version "${BUNDLER_VERSION}"
WORKDIR /usr/src/app
# https registry breaks so often it's no longer funny
RUN echo "registry = 'http://registry.npmjs.org/'" >> /usr/local/etc/npmrc
# moar logs
RUN echo "loglevel=info" >> /usr/local/etc/npmrc
COPY Gemfile ./Gemfile
COPY Gemfile.* ./
RUN chown -R app:app /usr/src/app
USER app
RUN bundle install --jobs 8 --retry 3
USER root
# Then, npm install node modules
COPY package.json /tmp/npm/package.json
COPY frontend/*.json /tmp/npm/frontend/
RUN chown -R app:app /tmp/npm
USER app
RUN cd /tmp/npm && RAILS_ENV=production npm install
RUN mv /tmp/npm/frontend /usr/src/app/
# Finally, copy over the whole thing
USER root
COPY . /usr/src/app
RUN cp docker/Procfile .
RUN chown -R app:app /usr/src/app
USER app
RUN DATABASE_URL=sqlite3:///tmp/db.sqlite3 SECRET_TOKEN=foobar RAILS_ENV=production bundle exec rake assets:precompile
CMD ["./docker/web"]

@ -233,6 +233,19 @@ group :opf_plugins do
gem 'openproject-translations', git:'https://github.com/opf/openproject-translations.git', branch: 'release/5.0'
end
# TODO: Make this group :optional when bundler v10.x
# is matured enough that we can use this everywhere
# http://bundler.io/blog/2015/06/24/version-1-10-released.html
group :docker do
gem 'passenger'
# Used to easily precompile assets
gem 'sqlite3', require: false
gem 'rails_12factor', require: !!ENV['HEROKU']
gem 'health_check', require: !!ENV['HEROKU']
gem 'newrelic_rpm', require: !!ENV['HEROKU']
end
# Load Gemfile.local, Gemfile.plugins and plugins' Gemfiles
Dir.glob File.expand_path('../{Gemfile.local,Gemfile.plugins,lib/plugins/*/Gemfile}', __FILE__) do |file|
next unless File.readable?(file)

@ -278,6 +278,8 @@ GEM
virtus (>= 1.0.0)
gravatar_image_tag (1.2.0)
hashie (3.4.1)
health_check (1.5.1)
rails (>= 2.3.0)
hike (1.2.3)
htmldiff (0.0.1)
http-cookie (1.0.2)
@ -315,6 +317,7 @@ GEM
net-ssh (>= 2.6.5)
net-ssh (2.9.2)
netrc (0.11.0)
newrelic_rpm (3.14.1.311)
nokogiri (1.6.6.4)
mini_portile (~> 0.6.0)
non-stupid-digest-assets (1.0.4)
@ -327,6 +330,9 @@ GEM
parallel
parser (2.2.2.5)
ast (>= 1.1, < 3.0)
passenger (5.0.22)
rack
rake (>= 0.8.1)
pg (0.18.3)
poltergeist (1.7.0)
capybara (~> 2.1)
@ -389,8 +395,13 @@ GEM
loofah (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
rails_autolink (1.1.6)
rails (> 3.1)
rails_serve_static_assets (0.0.4)
rails_stdout_logging (0.0.4)
railties (4.2.4)
actionpack (= 4.2.4)
activesupport (= 4.2.4)
@ -490,6 +501,7 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
sqlite3 (1.3.11)
structured_warnings (0.2.0)
syck (1.0.5)
sys-filesystem (1.1.4)
@ -576,6 +588,7 @@ DEPENDENCIES
gon (~> 4.0)
grape (~> 0.10.1)
gravatar_image_tag (~> 1.2.0)
health_check
htmldiff
jruby-openssl
json_spec
@ -584,12 +597,14 @@ DEPENDENCIES
multi_json (~> 1.11.0)
mysql2 (~> 0.3.20)
net-ldap (~> 0.8.0)
newrelic_rpm
nokogiri (~> 1.6.6)
non-stupid-digest-assets
oj (~> 2.11.4)
omniauth
openproject-translations!
parallel_tests (~> 2.1.2)
passenger
pg (~> 0.18.3)
poltergeist
prototype-rails!
@ -607,6 +622,7 @@ DEPENDENCIES
rack_session_access
rails (= 4.2.4)
rails-observers
rails_12factor
rails_autolink (~> 1.1.6)
rb-readline (~> 0.5.1)
rdoc (>= 2.4.2)
@ -631,6 +647,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8)
simplecov (= 0.8.0.pre)
sprockets (~> 2.12.3)
sqlite3
svg-graph!
syck (~> 1.0.5)
sys-filesystem (~> 1.1.4)

@ -146,7 +146,7 @@ with OpenProject. For more information, see https://github.com/opf/openproject-c
[openproject@host] git clone https://github.com/opf/openproject-ce.git --branch stable/5 --depth 1
[openproject@host] cd openproject-ce
[openproject@host] gem install bundler
[openproject@host] bundle install --deployment --without postgres sqlite development test therubyracer
[openproject@host] bundle install --deployment --without postgres sqlite development test therubyracer docker
[openproject@host] npm install
```

@ -0,0 +1,3 @@
web: ./docker/web
worker: ./docker/worker
cron: ./docker/cron

@ -0,0 +1,26 @@
#!/bin/bash
set -e
IMAP_SSL=${IMAP_SSL:="true"}
IMAP_PORT=${IMAP_PORT:="993"}
IMAP_ENABLED="${IMAP_ENABLED:="true"}"
IMAP_CHECK_INTERVAL="${IMAP_CHECK_INTERVAL:=600}"
while true; do
if [ "$IMAP_ENABLED" = "true" ]; then
echo "[cron] Checking for new emails from IMAP"
bundle exec rake redmine:email:receive_imap \
host="${IMAP_HOST}" \
username="${IMAP_USERNAME}" \
password="${IMAP_PASSWORD}" \
ssl=${IMAP_SSL} \
port=${IMAP_PORT} \
allow_override="${IMAP_ALLOW_OVERRIDE}" || true
else
echo "[cron] IMAP email checking is disabled"
fi
echo "[cron] Rescheduling in ${IMAP_CHECK_INTERVAL}s"
sleep ${IMAP_CHECK_INTERVAL}s
done

@ -0,0 +1,211 @@
##############################################################
# Phusion Passenger Standalone uses a template file to
# generate an Nginx configuration file. The original template
# file can be found by running the following command:
#
# ls $(passenger-config about resourcesdir)/templates/standalone/config.erb
#
# You can create a copy of this template file and customize it
# to your liking. Just make sure you tell Phusion Passenger Standalone
# to use your template file by passing the --nginx-config-template
# parameter.
#
# *** NOTE ***
# If you customize the template file, make sure you keep an eye
# on the original template file and merge any changes.
# New Phusion Passenger features may require changes to the template
# file.
##############################################################
master_process on;
worker_processes 1;
daemon on;
error_log '<%= @options[:log_file] %>' <% if @options[:log_level] >= LVL_DEBUG %>info<% end %>;
pid '<%= @options[:pid_file] %>';
<% if Process.euid == 0 %>
<% if @options[:user] %>
<%# Run workers as the given user. The master process will always run as root and will be able to bind to any port. %>
user <%= @options[:user] %> <%= default_group_for(@options[:user]) %>;
<% else %>
<%# Prevent running Nginx workers as nobody. %>
user <%= current_user %> <%= default_group_for(current_user) %>;
<% end %>
<% end %>
events {
worker_connections 1024;
}
http {
log_format debug '[$time_local] $msec "$request" $status conn=$connection sent=$bytes_sent body_sent=$body_bytes_sent';
include '<%= PhusionPassenger.resources_dir %>/mime.types';
<% if @options[:ruby] %>
passenger_ruby <%= @options[:ruby] %>;
<% else %>
passenger_ruby <%= PlatformInfo.ruby_command %>;
<% end %>
<% if @options[:nodejs] %>
passenger_nodejs <%= @options[:nodejs] %>;
<% end %>
<% if @options[:python] %>
passenger_python <%= @options[:python] %>;
<% end %>
passenger_root '<%= PhusionPassenger.install_spec %>';
passenger_abort_on_startup_error on;
passenger_ctl cleanup_pidfiles <%= serialize_strset("#{@working_dir}/temp_dir_toucher.pid") %>;
passenger_ctl integration_mode standalone;
passenger_ctl standalone_engine nginx;
passenger_user_switching off;
<%= nginx_option :passenger_log_level, :log_level %>
<%= nginx_option :passenger_max_pool_size, :max_pool_size %>
<%= nginx_option :passenger_min_instances, :min_instances %>
<%= nginx_option :passenger_pool_idle_time, :pool_idle_time %>
<%= nginx_option :passenger_max_preloader_idle_time, :max_preloader_idle_time %>
<%= nginx_option :passenger_turbocaching, :turbocaching %>
<% if @options[:user] %>
passenger_user <%= @options[:user] %>;
passenger_default_user <%= @options[:user] %>;
passenger_analytics_log_user <%= @options[:user] %>;
<% else %>
passenger_user <%= current_user %>;
passenger_default_user <%= current_user %>;
passenger_analytics_log_user <%= current_user %>;
<% end %>
<% if @options[:instance_registry_dir] %>passenger_instance_registry_dir '<%= @options[:instance_registry_dir] %>';<% end %>
<% if @options[:data_buffer_dir] %>passenger_data_buffer_dir '<%= @options[:data_buffer_dir] %>';<% end %>
<% if @options[:rolling_restarts] %>passenger_rolling_restarts on;<% end %>
<% if @options[:resist_deployment_errors] %>passenger_resist_deployment_errors on;<% end %>
<% if !@options[:load_shell_envvars] %>passenger_load_shell_envvars off;<% end %>
<% if !@options[:friendly_error_pages].nil? -%>
passenger_friendly_error_pages <%= boolean_config_value(@options[:friendly_error_pages]) %>;
<% end %>
<% if @options[:union_station_gateway_address] %>
union_station_gateway_address <%= @options[:union_station_gateway_address] %>;
<% end %>
<% if @options[:union_station_gateway_port] %>
union_station_gateway_port <%= @options[:union_station_gateway_port] %>;
<% end %>
<% if @options[:union_station_gateway_cert] %>
union_station_gateway_cert -;
<% end %>
<% @options[:ctls].each do |ctl| %>
passenger_ctl '<%= ctl.split("=", 2)[0] %>' '<%= ctl.split("=", 2)[1] %>';
<% end %>
default_type application/octet-stream;
types_hash_max_size 2048;
server_names_hash_bucket_size 64;
client_max_body_size 20m;
access_log off;
keepalive_timeout 60;
underscores_in_headers on;
gzip on;
gzip_comp_level 3;
gzip_min_length 150;
gzip_proxied any;
gzip_types text/plain text/css text/json text/javascript
application/javascript application/x-javascript application/json
application/rss+xml application/vnd.ms-fontobject application/x-font-ttf
application/xml font/opentype image/svg+xml text/xml;
<% if @app_finder.multi_mode? %>
# Default server entry for mass deployment mode.
server {
<% if @options[:ssl] %>
<% if @options[:ssl_port] %>
listen <%= nginx_listen_address %>;
listen <%= nginx_listen_address_with_ssl_port %> ssl;
<% else %>
listen <%= nginx_listen_address %> ssl;
<% end %>
<% else %>
listen <%= nginx_listen_address %>;
<% end %>
root '<%= PhusionPassenger.resources_dir %>/standalone_default_root';
}
<% end %>
<% for app in @apps %>
server {
<% if app[:ssl] %>
<% if app[:ssl_port] %>
listen <%= nginx_listen_address(app) %>;
listen <%= nginx_listen_address_with_ssl_port(app) %> ssl;
<% else %>
listen <%= nginx_listen_address(app) %> ssl;
<% end %>
<% else %>
listen <%= nginx_listen_address(app) %>;
<% end %>
server_name <%= app[:server_names].join(' ') %>;
<% if app[:static_files_dir] %>
root '<%= app[:static_files_dir] %>';
<% else %>
root '<%= app[:root] %>/public';
<% end %>
passenger_app_root '<%= app[:root] %>';
passenger_enabled on;
passenger_app_env <%= app[:environment] %>;
passenger_spawn_method <%= app[:spawn_method] %>;
<% if app[:app_type] %>passenger_app_type <%= app[:app_type] %>;<% end %>
<% if app[:startup_file] %>passenger_startup_file <%= app[:startup_file] %>;<% end %>
<% if app[:concurrency_model] && app[:concurrency_model] != DEFAULT_CONCURRENCY_MODEL %>passenger_concurrency_model <%= app[:concurrency_model] %>;<% end %>
<% if app[:thread_count] && app[:thread_count] != DEFAULT_APP_THREAD_COUNT %>passenger_thread_count <%= app[:thread_count] %>;<% end %>
<% if app[:min_instances] %>passenger_min_instances <%= app[:min_instances] %>;<% end %>
<% if app[:restart_dir] %>passenger_restart_dir '<%= app[:restart_dir] %>';<% end %>
<% if app[:sticky_sessions] %>passenger_sticky_sessions on;<% end %>
<% if app[:sticky_sessions_cookie_name] %>passenger_sticky_sessions_cookie_name '<%= app[:sticky_sessions_cookie_name] %>';<% end %>
<% if app[:vary_turbocache_by_cookie] %>passenger_vary_turbocache_by_cookie '<%= app[:vary_turbocache_by_cookie] %>';<% end %>
<% if app[:union_station_key] %>
union_station_support on;
union_station_key <%= app[:union_station_key] %>;
<% end %>
<% if app[:ssl] %>
ssl_certificate <%= app[:ssl_certificate] %>;
ssl_certificate_key <%= app[:ssl_certificate_key] %>;
<% end %>
<% if @options[:meteor_app_settings] %>
passenger_meteor_app_settings <%= @options[:meteor_app_settings] %>;
<% end %>
<% app[:envvars].each_pair do |name, value| %>
passenger_env_var '<%= name %>' '<%= value %>';
<% end %>
# Rails asset pipeline support.
location ~ "^/assets/.+-([0-9a-f]{32}|[0-9a-f]{64})\..+" {
error_page 490 = @static_asset;
error_page 491 = @dynamic_request;
recursive_error_pages on;
if (-f $request_filename) {
return 490;
}
if (!-f $request_filename) {
return 491;
}
}
location @static_asset {
gzip_static on;
expires max;
add_header Cache-Control public;
add_header ETag "";
}
location @dynamic_request {
passenger_enabled on;
}
<% (ENV['NGINX_ADDITIONAL_SERVER_RULES'] || "").split(";").each do |rule| %>
<%= rule.chomp(";") %>;
<% end %>
}
passenger_pre_start <%= listen_url(app) %>;
<% end %>
}

@ -0,0 +1,23 @@
#!/bin/bash -e
BIND="${BIND:=0.0.0.0}"
PORT="${PORT:=8080}"
RAILS_ENV="${RAILS_ENV:="development"}"
MIGRATE="${MIGRATE:="false"}"
MIN_INSTANCES="${PASSENGER_MIN_INSTANCES:=1}"
MAX_INSTANCES="${PASSENGER_MAX_INSTANCES:=3}"
SPAWN_METHOD="${PASSENGER_SPAWN_METHOD:=smart}"
if [ "$MIGRATE" = "true" ]; then
echo "Migrating database..."
bundle exec rake db:migrate
fi
exec bundle exec passenger start \
-p $PORT \
-a "${BIND}" \
--min-instances "$MIN_INSTANCES" \
--max-pool-size "$MAX_INSTANCES" \
--spawn-method "$SPAWN_METHOD" \
--nginx-config-template "docker/nginx.conf.erb" \
--max-preloader-idle-time 0

@ -0,0 +1,2 @@
#!/bin/bash -e
exec bundle exec rake jobs:work
Loading…
Cancel
Save