Skip to content

Instantly share code, notes, and snippets.

@clcollins
Last active December 19, 2017 21:00
Show Gist options
  • Select an option

  • Save clcollins/211c5442ed2070f53336d9580462a828 to your computer and use it in GitHub Desktop.

Select an option

Save clcollins/211c5442ed2070f53336d9580462a828 to your computer and use it in GitHub Desktop.

Example Ruby On Rails development workflow with containers

This is an example of a workflow we've developed to develop Ruby on Rails applications in Docker containers, to assure the local development occurs in the same environment as the production workload. It also has the side benefit of making local development easier: every project can be built with whatever dependencies or libraries are desired, without having to try to install them on the developers' local machines. In addition, local tools and direct code manipulation are still possible, making development feel entirely native to the local machine.

This general workflow works for other languages just as easily. See the "What about other languages?" section, below.

Assumptions:

  1. You have a database container that will initialize itself on startup, setup DB & user/password
  2. You have a Ruby on Rails container
  3. The Rails container has scripts that will do a bundle install (install required Gems)
  4. The Rails container will configure it's database.yml with values from the DB initialization+
  5. Your docker-compose.yml file (example below) lives in the top-level directory alongside your code

+Note: via some mechanism - Docker/OpenShift "secrets", env variables, some file. If the DB container is dedicated, it could just write out the file.

Three important things for Rails developers

Live Feedback

It's important that changes to code are immediately available to view. Looking at the example docker-compose.yml, there is a volume mount which mounts the directory with the code into the container's document root: .:/var/www/current. Along with Rails' live reload in development mode, this allows for developers to make changes locally in their editor of choice and have them show up immediately in their development container.

In addition, a different entrypoint is used for development than production: /app-config-local-devel.sh will install the Gems and compile assets on startup, and then run rails server+ to start a local webserver viewable locally only, not viewable from outside of the system. The port is randomized, so multiple projects can be up at once, and a bash one-liner can parse Docker Compose output to open a browser to the correct port like so: google-chrome $(docker-compose port web 3000).

+Note: or bundle exec rails server if using bundler

The Rails console

Moreso than other languages, the Rails console is important for development in Ruby on Rails. We can use a live rails console with the docker exec (or for ease of use, the docker-compose exec) command. Once the database and web containers are running, in another terminal window: docker-compose exec web rails console+

+Note: Or docker-compose exec bundle exec rails console if using bundler.

Live Logs

By mounting .devel/log:/var/log, this exposes live logs that can be viewed, tailed, etc. from a terminal or tools on the host machine, and logs are retained even when containers are destroyed & recreated.

How orchestration works

Development occurs with the same database image that is used in production.

During development with the Rails image, code is mounted into /var/www/current via a volume mount. On startup, the /app-config-local-devel.sh script will install any Gems required and compile assets, then start the rails server for testing.

To produce a production Rails image, the same image is used as a parent image, and Docker ONBUILD commands are used to inject code and configure the app when the image is minted. For example, the last three lines of the Rails Dockerfile would be something like:

ENTRYPOINT [ "/startup.sh" ]
ONBUILD ADD . /var/www/current
ONBUILD RUN /build.sh

The /build.sh script handles the all the same Gem installs and asset compiles, but does so during the build itself. The production entrypoint (in this example, /startup.sh) ends up having nothing to do but start passenger/unicorn/insert-your-choice-of-webservers.

The code then builds with a dummy Dockerfile that has nothing but the FROM and LABEL maintainer lines in it. This allows the re-use of the Rails image as a parent for all Rails projects, so they share common layers, pull and push to registries faster, and use less storage space. (In our own case, our CI server just runs echo "FROM parentimage\nLABEL maintainer AUTOBUILDER" > Dockerfile at the beginning of the build process, and we don't maintain a Dockerfile for each individual project.)

What about other languages

Arguably, this workflow works just as easily, if not more easily, with other languages. The key components are the mounting of the local repo into the document root or target directory of the container, and the ability to use docker-compose exec to run commands within the container. The simplest example might be a non-web python script:

# docker-compose.yml
version: '2'
services:
  my_script:
    image: python:latest
    volumes:
      - .:/opt/my_script

And docker-compose run to test the script:

docker-compose run my_script /opt/my_script/script.py

The Ruby on Rails example with the /app-config-local-devel.sh concept could be easily extended to developing a PHP application within a framework with compiled assets (Drupal anyone?).

version: '2'
services:
db:
image: some_mysql_db_image
volumes:
- .devel/mysql:/var/lib/mysql
- .devel/log:/var/log
- .devel/conf:/conf
ports:
- 127.0.0.1::3306
web:
image: some_rails_image
volumes:
- .devel/conf:/conf
- .devel/bundle:/bundle
- .:/var/www/current
- .devel/conf/database.yml:/var/www/current/config/database.yml
links:
- "db:db"
ports:
- 127.0.0.1::3000
environment:
DB_ENV_DB_TYPE: mysql
depends_on:
- db
entrypoint: /app-config-local-devel.sh
working_dir: /var/www/current
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment