Deploy Jekyll Site to Heroku
Generally there are 2 ways to deploy Jekyll static site to Heroku.
- Deploy as rails app using web server gem such as
thin
. - Deploy as Docker image running
nginx
service.
In this post, I chose 2nd approach since I like to use Docker image.
Prepare Dockerfile
We will serve this Jekyll site via nginx service. In order to map Heroku port
number to our nginx service, we will need to use below default.conf
template.
server {
# Placeholder for Heroku port
listen $PORT;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
Dockerfile.
FROM nginx:alpine
# copy nginx `default.conf`
COPY nginx/default.conf /etc/nginx/conf.d/
# copy Jekyll generated files
COPY _site /usr/share/nginx/html/
# replace $PORT placeholder with HEROKU given port in default.conf and run nginx service
CMD sed -i -e 's/$PORT/'"$PORT"'/g' /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
You can named this file as Dockerfile
or have a suffix to indiciate the
Heroku process. For instance, if you want to run this application as web
process in Heroku, you could name it as Dockerfile.web
. This works well if
you have multiple Dockerfile
s with different process such as web
, worker
, or
clock
. Later on, when you run heroku container:push --recursive
, it will
build all the Dockerfile
s with the correct suffix and push them to Heroku docker
registry.
Configure Github Action
Even without Github Action, all I need is Heroku CLI to deploy this Docker image using below commands.
> bundle exec jekyll build
> heroku container:login
> heroku container:push -a <HEROKU_APP_NAME> --resursive
> heroku container:release -a <HEROKU_APP_NAME> web
Basically, in Github Actions, we are running above commands. Below is the full Github Action workflow yaml.
name: Push Container to Heroku
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Retrieve Gem Bundles Cache
uses: actions/cache@v2
with:
path: _bundle
key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gems-
- name: Generate site
run: |
mkdir -p _bundle/cache
chmod a+rwx -R .
docker-compose run -e JEKYLL_ENV=production jekyll bundle install
docker-compose run -e JEKYLL_ENV=production jekyll bundle exec jekyll build --trace
- name: Login to Heroku Container registry
env:
# run `heroku authorizations:create` to generate the HEROKU_API_KEY
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: heroku container:login
- name: Build and push
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: heroku container:push -a ${{ secrets.HEROKU_APP_NAME }} --recursive
- name: Release
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: heroku container:release -a ${{ secrets.HEROKU_APP_NAME }} web
With a push to master
branch, it will trigger a build which will
- Checkout code from
master
branch. - Generate Jekyll static files.
- Build and push the Docker image to heroku registry.
- Deploy the Docker image to Heroku with
web
process.
Conclusion
This approach not only work for static site, but also for other types of application such as Rails application. Having a Dockerfile will give you full control on what other linux packages you wish to install within the application server instance such as ImageMagick. It will also give you consistent environment between running your application on your local machine and on Heroku.