This content originally appeared on HackerNoon and was authored by Addo Boakye Forison
Dockerizing your Ruby on Rails with a React front-end application can dramatically improve your development workflow and deployment process. By creating a standardized environment for your app, you ensure consistent behavior across different stages of development, testing, production, and even across different systems. In fact, it is designed to minimize issues related to system differences. This guide will walk you through the essential steps to get your Rails and React app running smoothly in Docker containers.
Why Dockerize an Application?
Consistency Across Environments:
Docker ensures that the application runs the same way regardless of where it is deployed, whether on a developer's machine, a testing environment, or a production server. This consistency is achieved by containerizing all dependencies and configurations.
\
Dependency Management:
Docker containers include all necessary dependencies for the application to run. This means that variations in system libraries or missing dependencies on different systems do not affect the application's functionality.
\
Isolation:
Docker containers run in isolation from each other and from the host system. This isolation prevents conflicts between different applications and their dependencies on the same system.
\
Portability:
Docker containers can be easily moved and run on any system that supports Docker, whether it is a local machine, a cloud service, or a dedicated server. This makes the application highly portable and flexible in terms of deployment.
\ NB: A knowledge of Docker syntax is required
\ Dockerization involves two key concepts: images and containers. Images serve as blueprints for containers, containing all the necessary information to create a container, including dependencies and deployment configurations. A container is a runtime instance of an image, comprising the image itself, an execution environment, and runtime instructions. Docker in general, establishes a standard for shipping software.
\ To explain Docker with a simple analogy: think of containers as the shipping containers in a yard, images as the items placed inside these containers, and the shipping vessel as the system on which the containers run.
\
Whenever you set up and build your application, certain environment configurations are necessary. For example, you cannot run a Rails application without a Ruby environment installed on your system. Similarly, you cannot run a React application without Node.js
, and you cannot install React packages without a Node package manager like npm
or Yarn
etc.
\ Since the container runs in isolation from the user’s system, we are going to make all these packages available in our container just like we would have done in case we built it directly on our system, thus, the container will act as a system on it own, like a virtual machine. There are differences between docker and virtual machine but this example is just to explain further.
\
Now, let’s go ahead and dockerize the Rails application. To do this, we will need three files in our Rails application: a Dockerfile
, a docker-compose.yml
, and a bin/docker-entrypoint
. Let’s examine each of these files in detail.
\ NB: A knowledge of Docker syntax is required
==Dockerfile==
The Dockerfile
is a blueprint for creating a Docker container. It contains a series of instructions that Docker uses to build an image, which can then be used to run containers. Let's break down a Dockerfile
for a Ruby on Rails and React application:
. Base Image
ARG RUBY_VERSION=3.1.4
FROM ruby:$RUBY_VERSION
ARG RUBY_VERSION=3.1.4
: Defines a build argument namedRUBY_VERSION
with a default value of3.1.4
. This can be overridden at build time.
\
FROM ruby:$RUBY_VERSION
: Uses theruby
base image with the version specified byRUBY_VERSION
. This sets up the container with the Ruby runtime. Just like I mentioned earlier, to run a Rails application, you need to have Ruby installed.
2. Install Dependencies
RUN apt-get update -qq && \
apt-get install -y build-essential libvips bash bash-completion libffi-dev tzdata postgresql curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man
apt-get update -qq
: Updates the package list from the repositories, with-qq
for quiet output.
\
apt-get install -y
…: Installs various packages:build-essential
: Essential packages for building software (like GCC).libvips
: Library for image processing.bash
,bash-completion
: Bash shell and its auto-completion.libffi-dev
: Foreign Function Interface library.tzdata
: Time zone data.postgresql
: PostgreSQL database client.curl
: Tool to transfer data from URLs.\
apt-get clean
: Cleans up the local repository of retrieved package files.
\
rm -rf /var/lib/apt/lists/ /usr/share/doc /usr/share/man
: Removes package lists and documentation to reduce image size.
3. Install Node.js and Yarn
RUN curl -fsSL https://deb.nodesource.com/setup_current.x | bash - && \
apt-get install -y nodejs && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && \
apt-get install -y yarn
curl -fsSL https://deb.nodesource.com/setup_current.x | bash -
: Downloads and runs the NodeSource setup script to install Node.js.
\
apt-get install -y nodejs
: Installs Node.js.
\
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
: Adds the Yarn GPG key to verify its packages.
\
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
: Adds Yarn's repository to the list of sources.
\
apt-get update && apt-get install -y yarn
: Updates the package list and installs Yarn.
4. Environment Variables
ENV NODE_OPTIONS=--openssl-legacy-provider
ENV NODE_OPTIONS=--openssl-legacy-provider
: Sets an environment variable to enable legacy OpenSSL support for Node.js.
5. Set Working Directory
WORKDIR /rails
WORKDIR /rails
: Sets the working directory for subsequent instructions to/rails
.
6. Build Arguments and Environment Variables
ARG RAILS_ENV
ENV RAILS_ENV=$RAILS_ENV
ARG RAILS_ENV
: Defines a build argument namedRAILS_ENV
for specifying the Rails environment (likedevelopment
,test
,production
).
\
ENV RAILS_ENV=$RAILS_ENV
: Sets the environment variableRAILS_ENV
to the value of the build argument.
7. Install Application Gems
COPY Gemfile Gemfile.lock ./
RUN bundle install
COPY Gemfile Gemfile.lock ./
: Copies theGemfile
andGemfile.lock
to the working directory.
\
RUN bundle install
: Installs Ruby gems specified in theGemfile
.
8. Install Front-end Dependencies
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY package.json yarn.lock ./
: Copies thepackage.json
andyarn.lock
to the working directory.
\
RUN yarn install --frozen-lockfile
: Installs front-end dependencies using Yarn, ensuring it uses the exact versions inyarn.lock
.
9. Copy Application Code
COPY . .
COPY . .
: Copies all application code to the working directory.
10. Pre-compile Bootsnap Code
RUN bundle exec bootsnap precompile --gemfile app/ lib/
RUN bundle exec bootsnap precompile --gemfile app/ lib/
: Pre-compiles Bootsnap cache for faster Rails application boot times. Bootsnap is a gem that speeds up Ruby and Rails boot times by caching expensive computations.
11. Pre-compile Assets for Production
RUN if [ "$RAILS_ENV" = "production" ]; then \
SECRET_KEY_BASE=1 bin/rails assets:precompile; \
fi
RUN if [ "$RAILS_ENV" = "production" ]; then
…: Conditionally runs the asset pre-compilation only ifRAILS_ENV
is set toproduction
. This step is crucial for preparing assets for a production environment.
12. Entrypoint Script
COPY bin/docker-entrypoint /rails/bin/
RUN chmod +x /rails/bin/docker-entrypoint
COPY bin/docker-entrypoint /rails/bin/
: Copies a custom entrypoint script to the container.
\
RUN chmod +x /rails/bin/docker-entrypoint
: Makes the entrypoint script executable.
13. Define Entrypoint and Command
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
EXPOSE 5000 // you can use any port of your choice
CMD ["./bin/rails", "server"]
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
: Sets the entrypoint script that will run when the container starts. This script typically sets up the environment, prepares the database, and starts the application.
\
EXPOSE 5000
: Indicates that the container listens on port 5000. This is a documentation feature and does not publish the port.
\
CMD ["./bin/rails", "server"]
: Specifies the default command to run when the container starts, which is to start the Rails server.
==docker-compose.yml==
The docker-compose.yml
file is used to define and run multi-container Docker applications. It allows you to configure your application's services, networks, and volumes in a single file. In this case, we are going to use two services. Here’s the docker-compose.yml
file for the Rails application:
1. Database Service (db
)
codedb:
image: postgres:14.2-alpine
container_name: demo-postgres-14.2
volumes:
- postgres_data:/var/lib/postgresql/data
command: "postgres -c 'max_connections=500'"
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "5432:5432"
image: postgres:14.2-alpine
: Specifies the Docker image to use for this service. In this case, it's the PostgreSQL 14.2 image based on the Alpine Linux distribution. Alpine images are known for their small size, which can help keep the overall image size down.
\
container_name: demo-postgres-14.2
: Names the containerdemo-postgres-14.2
. This name is used to reference the container in commands and logs.
\
volumes
:postgres_data:/var/lib/postgresql/data:
Mounts a named volumepostgres_data
to/var/lib/postgresql/data
inside the container. This directory is where PostgreSQL stores its data, ensuring that the database data persists between container restarts.\
command: "postgres -c 'max_connections=500'"
: Overrides the default command of the PostgreSQL image. It starts PostgreSQL with a configuration option to increase the maximum number of connections to 500.
\
environment
:POSTGRES_DB: ${POSTGRES_DB}
: Sets the name of the default database to create, using an environment variablePOSTGRES_DB
.POSTGRES_USER: ${POSTGRES_USER}
: Sets the default username for accessing the PostgreSQL database, using thePOSTGRES_USER
environment variable.POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
: Sets the password for the default user, using thePOSTGRES_PASSWORD
environment variable.\
ports
:"5432:5432"
: Maps port 5432 on the host to port 5432 in the container. This allows access to PostgreSQL on the host machine via port 5432.
2. Web Application Service (demo-web
)
codedemo-web:
build:
context: .
args:
- RAILS_ENV=${RAILS_ENV}
command: "./bin/rails server -b 0.0.0.0"
environment:
- RAILS_ENV=${RAILS_ENV}
- POSTGRES_HOST=${POSTGRES_HOST}
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- RAILS_MASTER_KEY=${RAILS_MASTER_KEY}
volumes:
- .:/rails
- app-storage:/rails/storage
depends_on:
- db
ports:
- "3000:3000"
build:
context: .
: Specifies the build context for the Docker image. In this case,.
refers to the current directory. This means Docker will use the Dockerfile in the current directory to build the image.args
:RAILS_ENV=${RAILS_ENV}
: Passes theRAILS_ENV
build argument to the Docker build process, allowing you to specify the Rails environment (likedevelopment
,test
, orproduction
).\
command: "./bin/rails server -b 0.0.0.0"
: Overrides the default command of the Docker image. Starts the Rails server and binds it to all network interfaces (0.0.0.0
), which is necessary for the service to be accessible from outside the container.\
environment:
RAILS_ENV=${RAILS_ENV}
: Sets the Rails environment inside the container using theRAILS_ENV
environment variable.POSTGRES_HOST=${POSTGRES_HOST}
: Sets the PostgreSQL host address.POSTGRES_DB=${POSTGRES_DB}
: Sets the database name.POSTGRES_USER=${POSTGRES_USER}
: Sets the PostgreSQL user.POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
: Sets the PostgreSQL user password.RAILS_MASTER_KEY=${RAILS_MASTER_KEY}
: Sets the Rails master key, which is used for encrypting credentials and other secrets.\
volumes
:.:/rails
: Mounts the current directory (where thedocker-compose.yml
file is located) to/rails
inside the container. This allows you to edit files on your host and have those changes reflected inside the container.app-storage:/rails/storage
: Mounts a named volumeapp-storage
to/rails/storage
inside the container. This is typically used for storing Rails-specific files such as logs, uploads, and cached files.\
depends_on
:db
: Ensures that thedemo-web
service waits for thedb
service to be ready before starting. Docker Compose handles the order of starting services based on this setting.ports:
"3000:3000"
: Maps port 3000 on the host to port 3000 in the container. This allows you to access the Rails application on the host machine via port 3000.Volumes
codevolumes:
postgres_data:
app-storage:
postgres_data
: Defines a named volumepostgres_data
used by thedb
service to persist PostgreSQL data.app-storage
: Defines a named volumeapp-storage
used by thedemo-web
service to persist application-specific data, such as uploads and logs.
==bin/docker-entrypoint==
The bin/docker-entrypoint
script is a crucial part of the Docker setup. It is executed when the container starts, and it typically handles environment setup, database preparation, and other initialization tasks needed before starting the main application. Here’s an example bin/docker-entrypoint
script and a detailed explanation of each part:
Shebang and Exit on Error
bashCopy code#!/bin/bash
set -e
#!/bin/bash
: This line specifies that the script should be run using the Bash shell.
\
set -e
: This instructs the script to exit immediately if any command returns a non-zero exit code. This helps ensure that if any step fails, the script stops execution, which can prevent subsequent steps from running in an invalid state.
\ Conditional Database Creation or Migration
# If running the rails server then create or migrate existing database
if [ "${*}" == "./bin/rails server" ]; then
./bin/rails db:create
./bin/rails db:prepare
fi
\
- if [ "${*}" == "./bin/rails server" ]; then … fi: This conditional statement checks if the command passed to the script (
"${*}"
) is./bin/rails server
. The*
is a special parameter that holds all the positional parameters passed to the script.
\
./bin/rails db
\: If the condition is met, this command will attempt to create the database. It is equivalent to running
rails db:create
which sets up the database as defined in the database configuration file (config/database.yml
).\
./bin/rails db
\: This command will run
rails db:prepare
, which ensures the database is set up and migrated. It will create the database if it doesn't exist and run migrations if the database is already created. This is a combination ofrails db:create
andrails db:migrate
.Executing the Main Process
bashCopy codeexec "${@}"
exec "${@}"
: This replaces the current shell process with the command passed as arguments to the script. The@
symbol holds all the positional parameters passed to the script. For example, if the script is called with./bin/rails server
, this line effectively runs./bin/rails server
as the main process of the container.
Conclusion
A well-crafted Dockerfile
is essential for creating a reliable and consistent environment for your Ruby on Rails and React application. By defining the base image, setting environment variables, and installing dependencies, you ensure that your application runs smoothly across various environments.
\ Docker not only streamlines your development process but also enhances the reliability of your application in production. There are areas of optimizations, but this is just a general overview of how to dockerize the rails application.
Full Script for the Resulting Dockerfile
, docker-compose.yml
and bin/docker-entrypoint
ARG RUBY_VERSION=3.1.4
FROM ruby:$RUBY_VERSION
# Install dependencies
RUN apt-get update -qq && \
apt-get install -y build-essential libvips bash bash-completion libffi-dev tzdata postgresql curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /usr/share/doc /usr/share/man
# Install Node.js and Yarn
RUN curl -fsSL https://deb.nodesource.com/setup_current.x | bash - && \
apt-get install -y nodejs && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && \
apt-get install -y yarn
# Set environment variable to enable legacy OpenSSL support
ENV NODE_OPTIONS=--openssl-legacy-provider
# Rails app lives here
WORKDIR /rails
# Set environment variable for the build
ARG RAILS_ENV
ENV RAILS_ENV=$RAILS_ENV
# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install
# Install frontend dependencies
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile --gemfile app/ lib/
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN if [ "$RAILS_ENV" = "production" ]; then \
SECRET_KEY_BASE=1 bin/rails assets:precompile; \
fi
# Entrypoint prepares the database.
COPY bin/docker-entrypoint /rails/bin/
RUN chmod +x /rails/bin/docker-entrypoint
# Use an absolute path for the entry point script
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start the server by default, this can be overwritten at runtime
EXPOSE 5000
CMD ["./bin/rails", "server"]
\
services:
db:
image: postgres:14.2-alpine
container_name: demo-postgres-14.2
volumes:
- postgres_data:/var/lib/postgresql/data
command: "postgres -c 'max_connections=500'"
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
ports:
- "5432:5432"
demo-web:
build:
context: .
args:
- RAILS_ENV=${RAILS_ENV}
command: "./bin/rails server -b 0.0.0.0"
environment:
- RAILS_ENV=${RAILS_ENV}
- POSTGRES_HOST=${POSTGRES_HOST}
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- RAILS_MASTER_KEY=${RAILS_MASTER_KEY}
volumes:
- .:/rails
- app-storage:/rails/storage
depends_on:
- db
ports:
- "3000:3000"
volumes:
postgres_data:
app-storage:
\ \
#!/bin/bash
set -e
# If running the rails server then create or migrate existing database
if [ "${*}" == "./bin/rails server" ]; then
./bin/rails db:create
./bin/rails db:prepare
fi
exec "${@}"
\
This content originally appeared on HackerNoon and was authored by Addo Boakye Forison
Addo Boakye Forison | Sciencx (2024-08-04T14:00:14+00:00) A Simplified Guide for the”Dockerazition” of Ruby and Rails With React Front-End App. Retrieved from https://www.scien.cx/2024/08/04/a-simplified-guide-for-thedockerazition-of-ruby-and-rails-with-react-front-end-app/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.