Docker Compose NGINX Reverse Proxy cache Jekyll GitHub Metadata
The end-goal is to access GitHub API, and download Gems, in a respectful
fashion by caching responses for a reasonable amount of time. All while also
reducing developer friction by reducing the resulting command required a simple
docker-compose up
to serve a test site locally!
Overview
When Jekyll GitHub Metadata plugin makes requests of GitHub API, we redirect to NGINX reverse proxy. The NGINX reverse proxy then either serves a cached response, or makes the request to GitHub’s API caches successful response(s) then replies to Jekyll with the data.
When Jekyll (via bundle
) attempts to install Ruby Gems, we have it check the
mounted cached docker volume first. If dependencies are not cached, or out of
date, then it’ll download and cache those packages too.
To help prevent cached artifacts, and authentication details, from being
tracked and pushed by Git we also must update the .gitignore
file. Though
readers may wish to add extra protections via a pre-commit
hook to add
additional restrictions for tracking .env
files.
- Overview
- Git configurations
- Jekyll configurations
- NGINX configurations
- Docker Compose configurations
- Notes and tips
Git configurations
.gitignore
.bundle
## Vim
*.swp
*.swo
## Development
.env
## Docker
docker-volumes
### Jekyll
_site
.sass-cache/
.jekyll-metadata
Notes for -- `.gitignore`
- Tip; check the manual for
githooks
, specifically thepre-commit
event, for hints on how to reject tracking.env
files, eg.man --pager='less --pattern="^\s+pre-commit"' githooks
Jekyll configurations
Gemfile
source "https://rubygems.org"
gem "jekyll", "~> 3.9.5"
gem "minima", "~> 2.5.1"
gem "jekyll-github-metadata"
# If you have any plugins, put them here!
group :jekyll_plugins do
gem "jekyll-feed", "~> 0.6"
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
# and associated library.
install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do
gem "tzinfo", "~> 1.2"
gem "tzinfo-data"
end
# Performance-booster for watching directories on Windows
gem "wdm", "~> 0.1.0", :install_if => Gem.win_platform?
##
# Required to make Docker happy
group :docker_compose do
gem "webrick"
gem "kramdown-parser-gfm"
end
group :custom_plugins do
gem 'nokogiri'
end
Notes for -- `Gemfile`
gem "jekyll", "~> 3.9.5"
is set explicitly to avoid SASS build errors introduced by Jekyll version 4 and, as to last update to this post, is the exact version used by GitHub Pages so setting should help avoid “it works on CI/CD” sorts of issuesgem "jekyll-github-metadata"
check the official repository for additional details and configuration considerations- Other than last few entries, as noted, all others are defaults taken from newly initialized Jekyll project
_config.yml
title: S0AndS0
description: >- # this means to ignore newlines until "baseurl:"
Miscellaneous tips, tricks, and thoughts from the author known as S0AndS0
baseurl: "/" # the subpath of your site, e.g. /blog
url: 'https://s0ands0.github.io'
##
# Make docker happy with metadata plugin
repository: S0AndS0/S0AndS0.github.io
# Build settings
markdown: kramdown
theme: minima
plugins:
- jekyll-feed
- jekyll-github-metadata
# Exclude from processing.
# The following items will not be processed, by default. Create a custom list
# to override the default setting.
exclude:
- .env
- Gemfile
- Gemfile.lock
- docker-compose
- docker-compose.yaml
- docker-volumes
- node_modules
- vendor/bundle/
- vendor/cache/
- vendor/gems/
- vendor/ruby/
Notes for -- `_config.yml`
repository
must be defined to make Jekyll GitHub Metadata plugin happy, check GitHub – Jekyll – Issue4705
for further details.exclude
should listdocker-compose
anddocker-volumes
related directory/file(-s) so as to avoid re-building when those locations have file changes.markdown: kramdown
is co-dependent with [Gemfile
][heading__gemfile]’sgem "kramdown-parser-gfm"
configuration, so if utilizing a different MarkDown transpiler things may need additional adjustments.
NGINX configurations
docker-compose/nginx/templates/reverse-proxy-cache_api.github.com.conf.template
# vim: filetype=nginx
# Use cache path we have permissions for writing to within Docker
proxy_cache_path /tmp/nginx-cache/api.github.com levels=1:2 keys_zone=api.github.com:10m;
server {
server_name api.github.com;
listen ${NGINX_LISTEN_PORT};
## Un-comment to possibly enable persistent logs
# access_log /var/log/nginx/api.github.com-access.log;
# error_log /var/log/nginx/api.github.com-error.log;
##
# Fix errors involving
#
# *1 upstream sent too big header while reading response header from upstream
proxy_busy_buffers_size 512k;
proxy_buffers 4 512k;
proxy_buffer_size 256k;
location / {
resolver 8.8.8.8;
proxy_pass https://api.github.com;
proxy_cache github;
proxy_cache_valid 200 302 1d;
proxy_ignore_headers Expires Cache-Control Set-Cookie X-Accel-Redirect X-Accel-Expires;
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
proxy_set_header User-Agent ${GITHUB_USER_NAME};
proxy_set_header Authorization "Token ${GITHUB_AUTH_TOKEN}";
}
}
Notes for -- `reverse-proxy-cache_api.github.com.conf.template`
- All paths above are from the perspective of running within a Docker container
proxy_cache_path
andproxy_cache_valid
have long lifetimes,1d
(one day), which may need reduced if lots of updates are consistently being pushed to GitHublisten
andproxy_set_header
environment variable values are expanded via theservices.nginx.image
internal Docker image script at/docker-entrypoint.d/20-envsubst-on-templates.sh
, injected viaservices.nginx.env_file
definition within the [docker-compose.yaml
][heading__dockercomposeyaml] file, and values of environment variables are set within the following [.env/nginx.env
][heading__envnginxenv] file.
Docker Compose configurations
.env/nginx.env
# shellcheck disable=all
NGINX_LISTEN_PORT=80
GITHUB_USER_NAME=Your-User-Name
GITHUB_AUTH_TOKEN='sOm3sEc4eTe'
Notes for -- `.env/nginx.env`
⚠ Replace values for GITHUB_USER_NAME
, and GITHUB_AUTH_TOKEN
, before
proceeding!
Tip, the value for GITHUB_AUTH_TOKEN
may be obtained via;
https://github.com/settings/tokens/new
docker-compose.yaml
version: '2'
##
services:
##
jekyll:
image: jekyll/jekyll:latest
container_name: service-jekyll
command: jekyll serve --watch --force_polling --verbose --trace --incremental
depends_on:
- nginx
volumes:
- .:/srv/jekyll
- ./docker-volumes/jekyll/bundle:/usr/local/bundle
environment:
## Downgrade to non-SSL if using `networks.net-services.ipam`
- PAGES_API_URL=http://api.github.com
## Or swap above with below if NGINX be playing with only one config
# - PAGES_API_URL=http://host-nginx
## Point URL(s) at internally static IP address(es)
extra_hosts:
- api.github.com:172.21.0.2
hostname: host-jekyll
networks:
- net-jekyll
- net-services
ports:
- 4000:4000
##
nginx:
image: nginx:stable-alpine
container_name: service-nginx
## Comment following to reduce output
command: [nginx-debug, '-g', 'daemon off;']
volumes:
- ./docker-compose/nginx/templates:/etc/nginx/templates:ro
## Mostly to allow NGINX write access to paths protected by permissions
- ./docker-volumes/nginx/cache:/tmp/nginx-cache
- ./docker-volumes/nginx/run:/var/run
- ./docker-volumes/nginx/logs:/var/logs/nginx
env_file:
- path: ./.env/nginx.env
required: true
hostname: host-nginx
networks:
- net-nginx
- net-services
##
networks:
net-jekyll:
name: net-jekyll
driver: bridge
net-nginx:
name: net-nginx
driver: bridge
## Network over which services may chatter to each other over
net-services:
name: net-services
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/16
ip_range: 172.21.0.0/24
gateway: 172.21.0.1
aux_addresses:
host-nginx: 172.21.0.2
host-jekyll: 172.21.0.3
Notes for -- `docker-compose.yaml`
- Most paths above are from the perspective of the host’s repository root, and
volumes
map to paths within a given Docker container. networks.net-services.ipam
, andservices.jekyll.extra_hosts
, may need adjusted on systems where the defined IP addresses are already claimed by another set of Docker containers.
Notes and tips
-
Reload
nginx
service within Dockerdocker compose nginx nginx -t && docker compose nginx nginx -s reload
-
Restart
jekyll
container/servicedocker compose restart jekyll
-
Recreate network(s) after changing
docker-compose.yaml
filedocker rm network net-services
-
Occasionally the following error(s) will pop, no idea why, and may be a bug in the plugin not respecting the
PAGES_API_URL
environment variable’s defined protocol and/or valueservice-jekyll | GitHub Metadata: Failed to open TCP connection to api.github.com:443 (Connection refused - connect(2) for "api.github.com" port 443)
-
The above, so far, has taken a little over sixteen hours to research, develop, and document for you dear reader(s)… So if it has helped ya, then feel free to show your appreciation in a way that’ll encourage similar publications.
Attribution
- Developer Github -- Authenticating to REST API
- Docker Docs -- Networks top-level elements
- Docker Docs -- Networks top-level elements -- IPAM
- Docker Docs -- docker network connect
- GitHub -- Jekyll Docker -- Caching
- Docker Hub -- NGINX -> "Running nginx as non-root user"
- Kaspars Dambis -- Using Nginx as Reverse Proxy to Mirror GitHub API
- Stack Overflow -- Using `--add-host` or `extra_hosts` with `docker-compose`
- Stack Overflow -- Communication between multiple docker-compose projects
- Stack Overflow -- How to change configuration of existing docker network
- Stack Overflow -- Using regex capture groups from both the locaiton and rewrite?