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\
\
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.
\
\
\
\
\
\
\
\
\
\
\
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"\
\
volumes:
postgres_data:/var/lib/postgresql/data: Mounts a named volume postgres_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 variable POSTGRES_DB.
POSTGRES_USER: ${POSTGRES_USER}: Sets the default username for accessing the PostgreSQL database, using the POSTGRES_USER environment variable.
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}: Sets the password for the default user, using the POSTGRES_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.
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 the RAILS_ENV build argument to the Docker build process, allowing you to specify the Rails environment (like development, test, or production).
\
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 the RAILS_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 the docker-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 volume app-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 the demo-web service waits for the db 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.
VolumesThe 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\
\ 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\
\
./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 of rails db:create and rails db:migrate.
Executing the Main ProcessA 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 "${@}"\
All Rights Reserved. Copyright , Central Coast Communications, Inc.