Konnor's Blog

Dockerizing Bridgetown

May 23, 2020

What is Bridgetownrb?

Bridgetownrb is a "Webpack-aware,
Ruby-powered static site generator
for the modern Jamstack era."


So what does this mean? To me it means it is a static site generator that uses Webpack under the hood and can source data from other places like a CMS or markdown files just like other static site generators such as Gatsby or Gridsome

Table Of Contents

Note:

If you would like to skip straight to building without explanations feel free to go to the I know what I’m doing section.

Prerequisites

There are only 2 prerequisites for this project.

Docker & Docker Compose. To check you have them, run the following:

docker -v
# Docker version 19.03.6, build 369ce74a3c
docker-compose -v
# docker-compose version 1.25.0, build unknown

Create a new directory

Now that we’ve confirmed Docker and Docker Compose are installed, lets setup the initial structure for Docker to pull down Bridgetownrb so we do not have to install it locally.

mkdir -p bridgetown-project
cd bridgetown-project

Docker Files

Adding a Dockerfile

I’m not goin to go too in depth into this Dockerfile, but the point of it is to be able to run a Docker container as a non-root user and still do everything you need to do. We’ll be using Alpine Linux to keep the image small.

Create a Dockerfile and add the following contents into it.

Dockerfile
FROM ruby:2.6-alpine3.11 as builder
RUN apk add --no-cache --virtual \\
#
# required
nodejs-dev yarn bash \\
tzdata build-base libffi-dev \\
#
# nice to haves
curl git \\
#
# Fixes watch file isses with things like HMR
libnotify-dev
FROM builder as bridgetownrb-app
# This is to fix an issue on Linux with permissions issues
ARG USER_ID=${USER_ID:-1000}
ARG GROUP_ID=${GROUP_ID:-1000}
ARG DOCKER_USER=${DOCKER_USER:-user}
ARG APP_DIR=${APP_DIR:-/home/user/bridgetown-app}
# Create a non-root user
RUN addgroup -g $GROUP_ID -S $GROUP_ID
RUN adduser --disabled-password -G $GROUP_ID --uid $USER_ID -S $DOCKER_USER
# Create and then own the directory to fix permissions issues
RUN mkdir -p $APP_DIR
RUN chown -R $USER_ID:$GROUP_ID $APP_DIR
# Define the user running the container
USER $USER_ID:$GROUP_ID
# . now == $APP_DIR
WORKDIR $APP_DIR
# COPY is run as a root user, not as the USER defined above, so we must chown it
COPY --chown=$USER_ID:$GROUP_ID Gemfile* $APP_DIR/
RUN gem install bundler
RUN bundle install
# For webpacker / node_modules
COPY --chown=$USER_ID:$GROUP_ID package.json $APP_DIR
COPY --chown=$USER_ID:$GROUP_ID yarn.lock $APP_DIR
RUN yarn install
CMD ["yarn", "start"]

Reference File on Github

Adding a docker-compose.yml

Now that we have a Dockerfile as our base, lets make it easy to call the Dockerfile without having to specify a bunch of build arguments.

Create a docker-compose.yml and add the following content:

# docker-compose.yml
version: '3'
services:
web:
build:
context: .
dockerfile: Dockerfile
args:
USER_ID: ${USER_ID:-1000}
GROUP_ID: ${GROUP_ID:-1000}
DOCKER_USER: ${DOCKER_USER:-user}
APP_DIR: ${APP_DIR:-/home/user/bridgetown-app}
command: bash -c "yarn start --host '0.0.0.0'"
ports:
- '4000:4000'
# Not totally necessary to open 4001, but it is used, so lets make it discoverable
- '4001:4001'
- '4002:4002'
volumes:
- .:${APP_DIR:-/home/user/bridgetown-app}
# this seperates node_modules from the host
- node_modules:${APP_DIR:-/home/user/bridgetown-app}/node_modules
volumes:
node_modules:

Reference File on Github

Adding docker.env

You’ll notice above that theres a bunch of ENV variables being used to substitute values. Now there’s a few ways to provide the ENV variables to Docker. I’ve found the easiest way to pass ENV variables is by sourcing a file with ENV variables.

To show you what this looks like lets create a ‘docker.env’ file.

docker.env
# Assign and export seperately to avoid masking return values.
USER_ID=$(id -u "$USER")
GROUP_ID=$(id -g "$USER")
export USER_ID
export GROUP_ID
export DOCKER_USER="user"
export APP_DIR="/home/$DOCKER_USER/bridgetown"

Now in order to pull these values into your shell environment run the following command:

source ./docker.env

This will now pull in your ENV variables for docker to use.

Note:

This is really only necessary for Linux users. Mac and Windows users should be fine to run without this script. It has not been tested however.

Adding .dockerignore

The final piece to this Docker puzzle is to create a .dockerignore. I stole the .gitignore provided by Bridgetownrb for this. It looks as follows:

.dockerignore
# Bridgetown
output
.bridgetown-cache
.bridgetown-metadata
.bridgetown-webpack
# Dependency folders
node_modules
bower_components
vendor
# Caches
.sass-cache
.npm
.node_repl_history
# Ignore bundler config.
/.bundle
# Ignore Byebug command history file.
.byebug_history
# dotenv environment variables file
.env
# Mac files
.DS_Store
# Yarn
yarn-error.log
yarn-debug.log*
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity
.git

Reference File on Github

Dependency Files

Adding a Gemfile

Alright, with the Docker setup above, we can now specify our dependency files.

The first step is to create a Gemfile. Create a Gemfile as follows:

Gemfile
source "https://rubygems.org"
gem "bridgetown", "~> 0.15.0"

This will tell bundler to install bridgetown from Rubygems.org

Adding a package.json

Create a package.json structured similarly to the one below:

package.json
{
"name": "bridgetown-site",
"version": "1.0.0",
"private": true
}

Adding lockfiles

Almost done with the setup I promise!

Finally, lets create 2 empty lockfiles.

The 2 lockfiles are yarn.lock and Gemfile.lock

touch yarn.lock Gemfile.lock

Generating a project

File structure prior to generation

Your file structure should look as follows if you followed the above steps.

tree -L 1 -a .
.
├── docker-compose.yml
├── docker.env
├── Dockerfile
├── .dockerignore
├── Gemfile
├── Gemfile.lock
├── package.json
└── yarn.lock

Reference Branch on Github

Running the Generation Command

source ./docker.env && docker-compose run --rm bridgetown new . --force

This will generate a new project for bridgetown

File Structure After Generation

tree -L 1 -a .
.
├── bridgetown.config.yml
├── docker-compose.yml
├── docker.env
├── Dockerfile
├── .dockerignore
├── frontend
├── Gemfile
├── Gemfile.lock
├── .git
├── .gitignore
├── package.json
├── plugins
├── README.md
├── src
├── start.js
├── sync.js
├── webpack.config.js
└── yarn.lock

Reference Branch on Github

Now, to start your server you can simply run:

source ./docker.env && docker-compose up --build

This will allow you to view Bridgetown welcome screen on localhost:4000

Useful Commands

Starting the server

If it’s your first time since generating the project, run

source ./docker.env && docker-compose up --build

If you have already built the container, you can simply do:

source ./docker.env && docker-compose up

Stopping the server

In another terminal to stop the server you can simply run:

docker-compose down --remove-orphans

Other commands

Sourcing ENV variables


This is only technically required once in a running terminal.
`source ./docker.env`

Run a command in an already running container:


`docker-compose exec web [command]`

Run a one-off command:


`docker-compose run --rm web [command]`

Upgrading package.json:

docker-compose run --rm web yarn upgrade
docker-compose down --remove-orphans
docker-compose up --build

Adding an npm package:

docker-compose run --rm web yarn add [package]
docker-compose down --remove-orphans
docker-compose up --build

Adding a gem

docker-compose run --rm web bundle add [gem]
docker-compose down --remove-orphans
docker-compose up --build

The below is a TLDR / reference version of the above. To skip to the links sections click the link below.


Links section

I know what I'm doing

mkdir -p bridgetown-project && cd bridgetown-project
touch Gemfile Gemfile.lock package.json yarn.lock \\
.dockerignore docker-compose.yml Dockerfile docker.env
Dockerfile
FROM ruby:2.6-alpine3.11 as builder
RUN apk add --no-cache --virtual \\
#
# required
nodejs-dev yarn bash \\
tzdata build-base libffi-dev \\
#
# nice to haves
curl git \\
#
# Fixes watch file isses with things like HMR
libnotify-dev
FROM builder as bridgetownrb-app
# This is to fix an issue on Linux with permissions issues
ARG USER_ID=${USER_ID:-1000}
ARG GROUP_ID=${GROUP_ID:-1000}
ARG DOCKER_USER=${DOCKER_USER:-user}
ARG APP_DIR=${APP_DIR:-/home/user/bridgetown-app}
# Create a non-root user
RUN addgroup -g $GROUP_ID -S $GROUP_ID
RUN adduser --disabled-password -G $GROUP_ID --uid $USER_ID -S $DOCKER_USER
# Create and then own the directory to fix permissions issues
RUN mkdir -p $APP_DIR
RUN chown -R $USER_ID:$GROUP_ID $APP_DIR
# Define the user running the container
USER $USER_ID:$GROUP_ID
# . now == $APP_DIR
WORKDIR $APP_DIR
# COPY is run as a root user, not as the USER defined above, so we must chown it
COPY --chown=$USER_ID:$GROUP_ID Gemfile* $APP_DIR/
RUN gem install bundler
RUN bundle install
# For webpacker / node_modules
COPY --chown=$USER_ID:$GROUP_ID package.json $APP_DIR
COPY --chown=$USER_ID:$GROUP_ID yarn.lock $APP_DIR
RUN yarn install
CMD ["yarn", "start"]
docker-compose.yml
version: '3'
services:
web:
build:
context: .
dockerfile: Dockerfile
args:
USER_ID: ${USER_ID:-1000}
GROUP_ID: ${GROUP_ID:-1000}
DOCKER_USER: ${DOCKER_USER:-user}
APP_DIR: ${APP_DIR:-/home/user/bridgetown-app}
command: bash -c "yarn start --host '0.0.0.0'"
ports:
- '4000:4000'
# Not totally necessary to open 4001, but it is used, so lets make it discoverable
- '4001:4001'
- '4002:4002'
volumes:
- .:${APP_DIR:-/home/user/bridgetown-app}
# this seperates node_modules from the host
- node_modules:${APP_DIR:-/home/user/bridgetown-app}/node_modules
volumes:
node_modules:
docker.env
# Assign and export seperately to avoid masking return values.
USER_ID=$(id -u "$USER")
GROUP_ID=$(id -g "$USER")
export USER_ID
export GROUP_ID
export DOCKER_USER="user"
export APP_DIR="/home/$DOCKER_USER/bridgetown"
.dockerignore
# Bridgetown
output
.bridgetown-cache
.bridgetown-metadata
.bridgetown-webpack
# Dependency folders
node_modules
bower_components
vendor
# Caches
.sass-cache
.npm
.node_repl_history
# Ignore bundler config.
/.bundle
# Ignore Byebug command history file.
.byebug_history
# dotenv environment variables file
.env
# Mac files
.DS_Store
# Yarn
yarn-error.log
yarn-debug.log*
.pnp/
.pnp.js
# Yarn Integrity file
.yarn-integrity
.git
Gemfile
# Gemfile
source "https://rubygems.org"
gem "bridgetown", "~> 0.15.0"
package.json
{
"name": "bridgetown-site",
"version": "1.0.0",
"private": true
}
source ./docker.env
docker-compose run --rm web bridgetown new . --force
docker-compose up --build

Navigate to localhost:4000 and bam! up and running!

Bridgetown

Bridgetownrb

Bridgetown Getting Started

Github

Github Reference Repo

Going Forward

This blog post was merely a setup blog post. My next blog post will detail creating a portfolio with TailwindCSS & Bridgetownrb.

This is a reference post to point people back to. So stay tuned for the next part of building with bridgetown.

And if you dont feel like waiting, go check out their documentation.


[Bridgetown Documentation](https://www.bridgetownrb.com/docs/)

Good luck building with Bridgetown and I hope this was useful!


Written by Konnor Rogers who currently works as a paramedic looking to transition into becoming a full time software developer. You should follow him on Twitter