This is a basis for creating a Ruby on Rails development environment using Docker. It is based on the development environment used for my book Sustainable Web Development with Ruby on Rails and can be used and customized to meet any needs for doing local development.
- Setting up dependencies for developing Rails apps is not easy, especially as operating systems change.
- Although Docker can solve this, it is difficult to use and difficult to configure.
This provides a template for a Dockerfile
in which your Rails app can run. It also provides a template for a
docker-compose.yml
file you can use to run dependent services like Postgres or Redis. It then provides several
shell scripts that wrap the functionality you will need to do work.
It is currently set up to produce a Debian-based ARM64 image, because this is what is needed to do work on Apple Silicon. Although Apple Silicon can emulate Intel and AMD-based images, the emulation is incomplete and, in particular, Chrome does not work. See Customizing below if you don't want to use ARM64 images.
- Make sure Docker is installed
- Clone this repo
- Open
bin/vars
and edit the values in there as instructed. You should only need to changeACCOUNT
,REPO
, andTAG
. - Run
bin/build
- Run
bin/start
- In another terminal, run
bin/exec bash
. You will now be "logged in" to the Docker container where your app can run. This Docker container has:- Ruby & Bundler
- Rails
- Node
- Chromium and chromedriver
This repo is intended to be the basis for your dev environment, so my recommendation is to create a new repo where you manage your dev-environment and seed it with these files.
There are four main points where you can customize things:
bin/vars
- this allows limited control over things like the image name and ports.docker-compose.yml.template
- If you need a different database, don't need redis, need elastic search, etc, you can add services here as needed. This version includes Postgres and Redis, so you can use those as examples for what to do. Most common pieces of infrastructure can be run in Docker, so you should be able to get what you need.bin/build
will currently replace the following variables when it's run:%TAG%
- The value ofTAG
frombin/vars
%REPO%
- The value ofREPO
frombin/vars
%ACCOUNT%
- The value ofACCOUNT
frombin/vars
%EXPOSE%
- The value ofEXPOSE
frombin/vars
%WORKDIR%
- The value ofWORKDIR
frombin/vars
%LOCAL_PORT%
- The value ofLOCAL_PORT
frombin/vars
%VOLUME_SOURCE%
- The full path to the current working directory.
Dockerfile.template
- this is the basis for theDockerfile
that is generated bybin/build
. You can add whatever you like in here, customizing anything as needed. You may wish to remove the many comments in there that explain how it's set up. You also may wish to modify the architecture. Basically, this is a vanillaDockerfile
that you can do what you want to, but it has two variables that are replaced bybin/build
:%EXPOSE%
- The value ofEXPOSE
frombin/vars
%WORKDIR%
- The value ofWORKDIR
frombin/vars
- The scripts in
bin/
- You can modify and enhance these scripts to provide more customizations as needed. I strongly recommend maintaing these scripts as no-arg, dependency-free shell scripts, because that will be the simplest and most reliable way to keep your dev environment consistent and working. Aliases and required command-line arguments are just annoying.
This was extracted from the toolchain I created for my book, Sustainable Web Development with Ruby on Rails. All the examples and code in that book were executed many times in this development environment. I have also been using this for the past two years at my startup to manage the development of two Rails apps and a Zendesk sidebar app.
Docker is confusing, poorly documented, and poorly designed, so if it makes you feel as stupid as it makes me feel, that is OK. Here is a bit of conceptual grounding to help understand what is going on.
- A
Dockerfile
is a set of instructions for a computer you would like to run. - Building a
Dockerfile
produces an Image. This is a set of bytes on your hard drive and you could consider this to be a clone of the hard drive of a computer you want to run. - Starting an image produces a Container. This is a virtualized computer running the image
- In
docker-compose.yml
, there are services, which describe the containers you want to run in unision. Because a container is a virtualized computer running an image, each service requires an image that will be run. All the containers are run on the same network and can see each other. - When you see the word Host that is your computer. That is where Docker is running. I cannot think of a more confusing and unintuitive term but that is what they went with.
To make another analogy, Dockerfile
is like source code to a class. An image is the class itself, and a container
is an object you created from that class. docker-compose.yml
is your program that integrates objects of several
classes together.
Inside the container, you can connect to Postgres like so:
> PGPASSWORD=postgres psql --host db --username postgres --port 5432
postgres=#
When you run Rails, you need to tell it to bind to 0.0.0.0
, so you can't just do bin/rails s
. Instead you
must:
> bin/rails s --binding 0.0.0.0
When you do that, your Rails app should be available to your localhost on port 9999 (or whatever value you set in bin/vars
for EXPOSE
)
- As few dependencies as possible
- Your computer is not your development environment, it runs your development environment
- Useful for working professionals
- Programmers should understand how their development environment works
- Flexibility
- Production Deployments
- Hiding details about how this works from the user
- A way to QA this on other platforms like Linux or Windows without me having to buy a Linux machine or a Windows machine.
- Ability to target more than just ARM64 without a lot of customizations
- Probably some Docker best practices I'm not aware of needing to consider
When running Chrome inside an AMD-based Docker container, Apple Silicon is unable to emulate certain system calls it needs, thus you cannot run Rails system tests in an AMD-based Docker container running on Apple Silicon.
See the answer above. Chrome is not available for ARM64-based Linux operating systems, however Chromium is.
Change this:
FROM arm64v8/ruby:3.1
to whatever base image you like, such as amd64/ruby:3.1
.
If you do that, you don't have to use chromium. You can remove this line:
RUN apt-get -y install chromium chromium-driver
You will need to replace it with a more convoluted set of invocations that has changed many times since I first created this repo, so I will not document them here, as they are likely to be out of date. I'm sorry about that, but Google does not care about making this process easy.
The Rails GitHub org has a repo called docked that ostensibly sets up a dev environment for Rails. It may evolve to be more useful, but here are the problems it has that this repo does not:
- It will not work on Apple Silicon for reasons mentioned above re: Chrome
- It does not provide a solution for running dependent infrastructure like Postgres which, in my experience, is much harder to do than getting Rails running.
- It requires setting up shell aliases, which I dislike.
- It uses an odd-numbered version of Node, and it's not a good idea to use that for development or production unless you are working on Node itself. Odd-numbered versions go end-of-life frequently and become unsupported. It's better and safer to use even-numbered versions.
- It is designed for beginners to programming and Rails, which is great because we need more Rails developers, but that is a different use-case than a development environment for professional, experienced Rails developers. And yes, I mean "professional" in the sense of "getting paid to write Rails" and not in whatever stupid way Uncle Bob means it.
docker-compose.yml
and Dockerfile
share some values, but Docker provides no easy mechanism for that that I could figure out. So, the files are generated.
Docker is a more generally useful skill to have, so I decided to focus on this and not learn Vagrant, which is less useful.
I would love some! I am not a Docker expert and I only have used this for the way I do Rails. I'd love for this to be more useful (see above).
A few things I am not interested in:
- Adding dependencies. I love Ruby and love writing command line apps in Ruby but Ruby's exsitence is not reliable, which is why the scripts are in bash.
- Non-Rails web development. I want this to be about Rails
- Deprecated or unsupported versions of things