Skip to content

Instantly share code, notes, and snippets.

@Chocksy
Last active July 12, 2024 02:41
Show Gist options
  • Save Chocksy/f81a07c81ffa838c5d09b04901a28222 to your computer and use it in GitHub Desktop.
Save Chocksy/f81a07c81ffa838c5d09b04901a28222 to your computer and use it in GitHub Desktop.
Docker for Rails + Puma + Nginx + Pagespeed + PostgreSQL + Redis + Memcached on Heroku | https://blog.epicpxls.com/docker-for-rails-puma-nginx-pagespeed-postgresql-redis-memcached-on-heroku-3272d88968f4

Docker for Rails + Puma + Nginx + Pagespeed + PostgreSQL + Redis + Memcached on Heroku

A detailed blog post is here: https://blog.epicpxls.com/docker-for-rails-puma-nginx-pagespeed-postgresql-redis-memcached-on-heroku-3272d88968f4

How to

Create a docker folder in the root of the app:

  • docker-compose.yml
  • docker
    • app
      • DockerFile
      • entrypoint.sh
    • web
      • DockerFile
      • nginx.conf
      • pagespeed.conf
      • perf.conf
      • routing.conf

Copy all the code from this gist and follow the blog post for more info.

Any comments or updates to this are welcome.

# docker/app/DockerFile
# Base image / change this value to what ruby version you need.
FROM ruby:2.6.6
# Install dependencies
RUN apt-get update -qq
ENV LANG C.UTF-8
# Application dependencies
# We use an external Aptfile for that, stay tuned
COPY Aptfile /tmp/Aptfile
RUN DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
build-essential libpq-dev nodejs \
$(grep -Ev '^\s*#' /tmp/Aptfile | xargs) && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
truncate -s 0 /var/log/*log
# Set an environment variable where the Rails app is installed to inside of Docker image:
ENV RAILS_ROOT /var/www/pxls
RUN mkdir -p $RAILS_ROOT
# Set working directory, where the commands will be ran:
WORKDIR $RAILS_ROOT
# Setting env up
# Add a script to be executed every time the container starts.
COPY docker/app/entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
ENV RAILS_ENV='production'
ENV RACK_ENV='production'
# Adding gems
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install --jobs 20 --retry 5 --without development test
# Adding project files
COPY . .
RUN bundle exec rake assets:precompile
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
# config/database.yml
development: &development
<<: *default
database: pxls_production
username: postgres
password:
test:
<<: *development
database: pxls_test
username: postgres
password:
production:
<<: *default
database: pxls_production
username: postgres
password:
host: db
# How to run: https://docs.docker.com/compose/rails/
# docker-compose run web rails new . --force --database=postgresql --skip-bundle
# docker-compose build
# docker-compose up
version: '3.9'
services:
app:
build:
context: .
dockerfile: ./docker/app/DockerFile
env_file: .env
depends_on:
- db
- redis
- memcached
redis:
image: redis:latest
container_name: epicpxls-redis
ports:
- 6380:6379
memcached:
container_name: epicpxls-memcached
image: memcached:latest
ports:
- 11212:11211
db:
image: postgres:10
volumes:
- ./tmp/db:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
web:
build:
context: .
dockerfile: ./docker/web/DockerFile
depends_on:
- app
ports:
- 80:80
# docker/app/entrypoint.sh
#!/bin/bash
set -e
# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid
# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
# Build the whole docker images.
# - we first copy all the variables from the figaro yml file to a .env file
# - then we build the docker images
# - we start the images
# - we create the database
# Call this with:
# - make build
build:
@ruby docker/env_vars.rb
@docker compose build
@docker compose up -d
@docker compose exec app bundle exec rails db:setup
# If you want to rebuild just one container then you can use:
# - make service=web rebuild
rebuild:
@docker compose up --build --force-recreate --no-deps -d ${service}
# Used to start the docker images
start:
@docker compose start
# Debug the application so we can use pry.
debug:
@docker compose run --service-ports app
# Clean the docker instances (It restarts everything but does not remove the containers.)
clean:
@docker compose down
@docker system prune --volumes --force
@rm -rf tmp/* || sudo rm -rf tmp/*
@mkdir -p tmp/pids && touch tmp/pids/.keep
# If you want to execute a command on the container. Run this like this:
# - make bash
bash:
@docker compose exec app bash
# See logs of the containers. Run with:
# - make logs
logs:
@docker compose logs -f --tail=0 app
# This is a template. Referenced variables (e.g. $RAILS_ROOT) need
# to be rewritten with real values in order for this file to work.
events {
use epoll;
worker_connections 4096;
multi_accept on;
}
http {
server_tokens off;
log_format l2met 'measure#nginx.service=$request_time request_id=$http_x_request_id';
default_type application/octet-stream;
include mime.types;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
client_body_buffer_size 10K;
client_header_buffer_size 4k;
client_max_body_size 8m;
large_client_header_buffers 2 4k;
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
upstream app_server {
server 'app:3000';
}
server {
# define your domain
server_name _;
# define the public application root
root $RAILS_ROOT/public;
index index.html;
# Include performance configuration
include /etc/nginx/perf.conf;
# Include the pagespeed configuration
include /etc/nginx/pagespeed.conf;
# Include routing configuration
include /etc/nginx/routing.conf;
}
}
pagespeed on;
# Needs to exist and be writable by nginx. Use tmpfs for best performance.
pagespeed FileCachePath /tmp/;
# All servers running a given release will have the same value for this header by default.
# If you would like to change the value reported, you can use the XHeaderValue directive to specify what to use instead:
pagespeed XHeaderValue "Powered by ngx_pagespeed";
# With PreserveUrlRelativity on, PageSpeed will keep URLs the way they were found.
pagespeed PreserveUrlRelativity on;
# HTML is case-insensitive, whereas XML and XHTML are not. Web performance Best Practices
# suggest using lowercase keywords, and PageSpeed can safely make that transformation in HTML documents.
pagespeed LowercaseHtmlNames on;
pagespeed AvoidRenamingIntrospectiveJavascript off;
# You can specify the maximum length (in bytes) of the content for PageSpeed to optimize.
# A setting of -1 means content of all lengths will be optimized, but risks server out-of-memory crashes.
pagespeed MaxCacheableContentLength -1;
# By using the CoreFilters set, as PageSpeed is updated with new filters, your site will get faster.
pagespeed RewriteLevel CoreFilters;
# If your site uses a non-standard attribute for URLs, PageSpeed won't know to rewrite them or the
# resources they reference. To identify them to PageSpeed, use the UrlValuedAttribute directive.
pagespeed UrlValuedAttribute img data-src image;
# By default, PageSpeed serves all HTML with Cache-Control: no-cache, max-age=0 because the
# transformations made to the page may not be cacheable for extended periods of time.
# pagespeed ModifyCachingHeaders off;
# pagespeed UseExperimentalJsMinifier on;
# Optimizations
## region CSS
### Inlines small CSS files into the HTML document.
pagespeed EnableFilters inline_css;
### Combines multiple CSS elements into one.
pagespeed EnableFilters combine_css;
### Moves CSS elements above <script> tags.
pagespeed EnableFilters move_css_above_scripts;
### Replace CSS tags with inline versions that include only the CSS used by the page.
pagespeed EnableFilters prioritize_critical_css;
### Rewrites CSS files to remove excess whitespace and comments, and, if enabled, rewrite or
### cache-extend images referenced in CSS files. In OptimizeForBandwidth mode, the minification
### occurs in-place without changing URLs.
pagespeed EnableFilters rewrite_css;
### Rewrite the CSS in style attributes by applying the configured rewrite_css filter to it.
pagespeed EnableFilters rewrite_style_attributes;
### Externalize large blocks of CSS into a cacheable file.
pagespeed EnableFilters outline_css;
### Moves CSS elements into the <head>.
pagespeed EnableFilters move_css_to_head;
### Rewrites resources referenced in any CSS file that cannot otherwise be parsed and minified.
pagespeed EnableFilters fallback_rewrite_css_urls;
## endregion
## region JS
### Convert synchronous use of Google Analytics API to asynchronous
pagespeed EnableFilters make_google_analytics_async;
### Externalize large blocks of JS into a cacheable file.
pagespeed EnableFilters outline_javascript;
### Adds source maps to rewritten JavaScript files.
pagespeed EnableFilters include_js_source_maps;
### Redirects JavaScript libraries to a JavaScript hosting service.
pagespeed EnableFilters canonicalize_javascript_libraries;
### Cache inlined resources in HTML5 local storage.
pagespeed EnableFilters local_storage_cache;
### Inlines small JS files into the HTML document.
pagespeed EnableFilters inline_javascript;
### Defers the execution of JavaScript in HTML until page load complete.
# pagespeed EnableFilters defer_javascript;
### Combines multiple script elements into one.
# pagespeed EnableFilters combine_javascript;
### Rewrites JavaScript files to remove excess whitespace and comments.
### In OptimizeForBandwidth mode, the minification occurs in-place without changing URLs.
# pagespeed EnableFilters rewrite_javascript;
## endregion
## region Images
### Works just like inline_preview_images, but uses smaller placeholder images and only serves them to mobile browsers.
pagespeed EnableFilters resize_mobile_images;
### Loads images when they become visible in the client viewport.
pagespeed EnableFilters lazyload_images;
### Adds width and height attributes to <img> tags that lack them.
pagespeed EnableFilters insert_image_dimensions;
### Replaces repeated inlined images with JavaScript that loads the image from the first occurence of the image.
pagespeed EnableFilters dedup_inlined_images;
### Uses inlined low-quality images as placeholders which will be replaced with original images once the web page is loaded.
# pagespeed EnableFilters inline_preview_images;
### Implied by rewrite_images. Replaces png and non-animated gif images with webp images on browsers that support the format.
# pagespeed EnableFilters convert_to_webp_lossless;
### Combine background images in CSS files into one sprite.
# pagespeed EnableFilters sprite_images;
### Optimizes images, re-encoding them, removing excess pixels, and inlining small images.
### In OptimizeForBandwidth mode, the minification occurs in-place without changing URLs.
# pagespeed EnableFilters rewrite_images;
### Makes images responsive by adding srcset with images optimized for various resolutions.
# pagespeed EnableFilters responsive_images;
### Implied by rewrite_images. Resizes images when the corresponding <img> tag specifies a smaller width and height.
# pagespeed EnableFilters resize_images;
## endregion
## region HTML
### Removes excess whitespace in HTML files (avoiding <pre>, <script>, <style>, and <textarea>).
pagespeed EnableFilters collapse_whitespace;
### Removes comments in HTML files (but not in inline JavaScript or CSS).
pagespeed EnableFilters remove_comments;
### Removes quotes around HTML attributes that are not lexically required.
pagespeed EnableFilters remove_quotes;
### Shortens URLs by making them relative to the base URL.
### !!! can't use it as it will trim the _path urls and that won't be good.
# pagespeed EnableFilters trim_urls;
### Add default types for <script> and <style> tags if the type attribute is not present and the
### page is not HTML5. The purpose of this filter is to help ensure that PageSpeed does not break HTML4 validation.
pagespeed EnableFilters pedantic;
### Perform browser-dependent in-place resource optimizations. (https://www.modpagespeed.com/doc/system#ipro)
pagespeed EnableFilters in_place_optimize_for_browser;
### Inserts <link rel="dns-prefetch" href="//www.example.com"> tags to reduce DNS resolution time.
pagespeed EnableFilters insert_dns_prefetch;
### Extends cache lifetime of CSS, JS, and image resources that have not otherwise been optimized, by signing URLs with a content hash.
pagespeed EnableFilters extend_cache;
## endregion
## region Authorization
pagespeed Domain *.epicpxls.com;
pagespeed Domain localhost;
## endregion
location ~ "\.pagespeed\.([a-z]\.)?[a-z]{2}\.[^.]{10}\.[^.]+" {
add_header "" "";
}
location ~ "^/pagespeed_static/" { }
location ~ "^/ngx_pagespeed_beacon$" { }
gzip on;
gzip_comp_level 5;
gzip_min_length 100;
gzip_vary on;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types *;
gzip_disable "MSIE [1-6]\.";
# deny requests for files that should never be accessed
location ~ /\. {
deny all;
access_log off;
log_not_found off;
return 404;
}
location ~* ^.+\.(rb|log)$ {
deny all;
}
location ~ ^/(robots.txt|humans.txt) {
access_log off;
log_not_found off;
expires max;
}
# serve static (compiled) assets directly if they exist (for rails production)
location ~* ^.+\.(?:css|cur|js|jpe?g|gif|htc|ico|png|html|xml|otf|ttf|eot|woff|woff2|svg)$ {
try_files $uri @rails;
access_log off;
# this does not work on the heroku buildpack we are using so we turn it off.
# gzip_static on; # to serve pre-gzipped version
expires max;
add_header Cache-Control public;
add_header Vary "Accept-Encoding";
# No need to bleed constant updates. Send the all shebang in one fell swoop.
tcp_nodelay off;
# Set the OS file cache.
open_file_cache max=3000 inactive=120s;
open_file_cache_valid 45s;
open_file_cache_min_uses 2;
open_file_cache_errors off;
# Some browsers still send conditional-GET requests if there's a
# Last-Modified header or an ETag header even if they haven't
# reached the expiry date sent in the Expires header.
add_header Last-Modified "";
add_header ETag "";
break;
}
# send non-static file requests to the app server
location / {
try_files $uri @rails;
}
location @rails {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://app_server;
}
# docker/web/DockerFile
FROM crunchgeek/nginx-pagespeed
# establish where Nginx should look for files
ENV RAILS_ROOT /var/www/pxls
# Set our working directory inside the image
WORKDIR $RAILS_ROOT
# create log directory
RUN mkdir log
# copy over static assets
COPY public/ public/
# Copy Nginx config template
COPY docker/web/nginx.conf /tmp/docker.nginx/
COPY docker/web/perf.conf docker/web/routing.conf docker/web/pagespeed.conf /etc/nginx/
# substitute variable references in the Nginx config template for real values from the environment
# put the final config in its place
RUN envsubst '$RAILS_ROOT' < /tmp/docker.nginx/nginx.conf > /etc/nginx/nginx.conf
EXPOSE 80
# Use the "exec" form of CMD so Nginx shuts down gracefully on SIGTERM (i.e. `docker stop`)
CMD [ "nginx", "-g", "daemon off;" ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment