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 named RUBY_VERSION with a default value of 3.1.4. This can be overridden at build time. FROM ruby:$RUBY_VERSION: Uses the ruby base image with the version specified by RUBY_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 named RAILS_ENV for specifying the Rails environment (like development, test, production). ENV RAILS_ENV=$RAILS_ENV: Sets the environment variable RAILS_ENV to the value of the build argument. 7. Install Application Gems COPY Gemfile Gemfile.lock ./ RUN bundle install COPY Gemfile Gemfile.lock ./: Copies the Gemfile and Gemfile.lock to the working directory. RUN bundle install: Installs Ruby gems specified in the Gemfile. 8. Install Front-end Dependencies COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile COPY package.json yarn.lock ./: Copies the package.json and yarn.lock to the working directory. RUN yarn install --frozen-lockfile: Installs front-end dependencies using Yarn, ensuring it uses the exact versions in yarn.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 if RAILS_ENV is set to production. 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 container demo-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 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. 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 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. Volumes codevolumes: postgres_data: app-storage: postgres_data: Defines a named volume postgres_data used by the db service to persist PostgreSQL data. app-storage: Defines a named volume app-storage used by the demo-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 of rails db:create and rails 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 "${@}" 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? 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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 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. Node.js npm Yarn 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. Dockerfile docker-compose.yml bin/docker-entrypoint NB: A knowledge of Docker syntax is required NB: A knowledge of Docker syntax is required Dockerfile Dockerfile 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: Dockerfile Dockerfile . Base Image ARG RUBY_VERSION=3.1.4 FROM ruby:$RUBY_VERSION ARG RUBY_VERSION=3.1.4 FROM ruby:$RUBY_VERSION ARG RUBY_VERSION=3.1.4: Defines a build argument named RUBY_VERSION with a default value of 3.1.4. This can be overridden at build time. ARG RUBY_VERSION=3.1.4 : Defines a build argument named RUBY_VERSION with a default value of 3.1.4 . This can be overridden at build time. ARG RUBY_VERSION=3.1.4 RUBY_VERSION 3.1.4 FROM ruby:$RUBY_VERSION: Uses the ruby base image with the version specified by RUBY_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. FROM ruby:$RUBY_VERSION : Uses the ruby base image with the version specified by RUBY_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. FROM ruby:$RUBY_VERSION ruby RUBY_VERSION 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 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 update -qq : Updates the package list from the repositories, with -qq for quiet output. apt-get update -qq -qq 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. 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 install -y ... 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. build-essential: Essential packages for building software (like GCC). build-essential : Essential packages for building software (like GCC). build-essential libvips: Library for image processing. libvips : Library for image processing. libvips bash, bash-completion: Bash shell and its auto-completion. bash , bash-completion : Bash shell and its auto-completion. bash bash-completion libffi-dev: Foreign Function Interface library. libffi-dev : Foreign Function Interface library. libffi-dev tzdata: Time zone data. tzdata : Time zone data. tzdata postgresql: PostgreSQL database client. postgresql : PostgreSQL database client. postgresql curl: Tool to transfer data from URLs. curl : Tool to transfer data from URLs. curl apt-get clean : Cleans up the local repository of retrieved package files. apt-get clean rm -rf /var/lib/apt/lists/ /usr/share/doc /usr/share/man: Removes package lists and documentation to reduce image size. rm -rf /var/lib/apt/lists/ /usr/share/doc /usr/share/man : Removes package lists and documentation to reduce image size. rm -rf /var/lib/apt/lists/ /usr/share/doc /usr/share/man 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 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. curl -fsSL https://deb.nodesource.com/setup_current.x | bash - : Downloads and runs the NodeSource setup script to install Node.js. curl -fsSL https://deb.nodesource.com/setup_current.x | bash - apt-get install -y nodejs: Installs Node.js. apt-get install -y nodejs : Installs Node.js. apt-get install -y nodejs curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -: Adds the Yarn GPG key to verify its packages. curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - : Adds the Yarn GPG key to verify its packages. 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: Adds Yarn's repository to the list of sources. 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. 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: Updates the package list and installs Yarn. apt-get update && apt-get install -y yarn : Updates the package list and installs Yarn. apt-get update && apt-get install -y yarn 4. Environment Variables ENV NODE_OPTIONS=--openssl-legacy-provider ENV NODE_OPTIONS=--openssl-legacy-provider ENV NODE_OPTIONS=--openssl-legacy-provider: Sets an environment variable to enable legacy OpenSSL support for Node.js. ENV NODE_OPTIONS=--openssl-legacy-provider : Sets an environment variable to enable legacy OpenSSL support for Node.js. ENV NODE_OPTIONS=--openssl-legacy-provider 5. Set Working Directory WORKDIR /rails WORKDIR /rails WORKDIR /rails: Sets the working directory for subsequent instructions to /rails. WORKDIR /rails : Sets the working directory for subsequent instructions to /rails . WORKDIR /rails /rails 6. Build Arguments and Environment Variables ARG RAILS_ENV ENV RAILS_ENV=$RAILS_ENV ARG RAILS_ENV ENV RAILS_ENV=$RAILS_ENV ARG RAILS_ENV: Defines a build argument named RAILS_ENV for specifying the Rails environment (like development, test, production). ARG RAILS_ENV : Defines a build argument named RAILS_ENV for specifying the Rails environment (like development , test , production ). ARG RAILS_ENV RAILS_ENV development test production ENV RAILS_ENV=$RAILS_ENV: Sets the environment variable RAILS_ENV to the value of the build argument. ENV RAILS_ENV=$RAILS_ENV : Sets the environment variable RAILS_ENV to the value of the build argument. ENV RAILS_ENV=$RAILS_ENV RAILS_ENV 7. Install Application Gems COPY Gemfile Gemfile.lock ./ RUN bundle install COPY Gemfile Gemfile.lock ./ RUN bundle install COPY Gemfile Gemfile.lock ./: Copies the Gemfile and Gemfile.lock to the working directory. COPY Gemfile Gemfile.lock ./ : Copies the Gemfile and Gemfile.lock to the working directory. COPY Gemfile Gemfile.lock ./ Gemfile Gemfile.lock RUN bundle install: Installs Ruby gems specified in the Gemfile. RUN bundle install : Installs Ruby gems specified in the Gemfile . RUN bundle install Gemfile 8. Install Front-end Dependencies COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile COPY package.json yarn.lock ./: Copies the package.json and yarn.lock to the working directory. COPY package.json yarn.lock ./ : Copies the package.json and yarn.lock to the working directory. COPY package.json yarn.lock ./ package.json yarn.lock RUN yarn install --frozen-lockfile: Installs front-end dependencies using Yarn, ensuring it uses the exact versions in yarn.lock. RUN yarn install --frozen-lockfile : Installs front-end dependencies using Yarn, ensuring it uses the exact versions in yarn.lock . RUN yarn install --frozen-lockfile yarn.lock 9. Copy Application Code COPY . . COPY . . COPY . .: Copies all application code to the working directory. COPY . . : Copies all application code to the working directory. COPY . . 10. Pre-compile Bootsnap Code RUN bundle exec bootsnap precompile --gemfile app/ lib/ 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. 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. RUN bundle exec bootsnap precompile --gemfile app/ lib/ 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 \ SECRET_KEY_BASE=1 bin/rails assets:precompile; \ fi RUN if [ "$RAILS_ENV" = "production" ]; then ...: Conditionally runs the asset pre-compilation only if RAILS_ENV is set to production. This step is crucial for preparing assets for a production environment. RUN if [ "$RAILS_ENV" = "production" ]; then ... : Conditionally runs the asset pre-compilation only if RAILS_ENV is set to production . This step is crucial for preparing assets for a production environment. RUN if [ "$RAILS_ENV" = "production" ]; then ... RAILS_ENV production 12. Entrypoint Script COPY bin/docker-entrypoint /rails/bin/ RUN chmod +x /rails/bin/docker-entrypoint 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. COPY bin/docker-entrypoint /rails/bin/ : Copies a custom entrypoint script to the container. COPY bin/docker-entrypoint /rails/bin/ RUN chmod +x /rails/bin/docker-entrypoint: Makes the entrypoint script executable. RUN chmod +x /rails/bin/docker-entrypoint : Makes the entrypoint script executable. RUN chmod +x /rails/bin/docker-entrypoint 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"] 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. 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. ENTRYPOINT ["/rails/bin/docker-entrypoint"] EXPOSE 5000: Indicates that the container listens on port 5000. This is a documentation feature and does not publish the port. EXPOSE 5000 : Indicates that the container listens on port 5000. This is a documentation feature and does not publish the port. EXPOSE 5000 CMD ["./bin/rails", "server"]: Specifies the default command to run when the container starts, which is to start the Rails server. CMD ["./bin/rails", "server"] : Specifies the default command to run when the container starts, which is to start the Rails server. CMD ["./bin/rails", "server"] docker-compose.yml docker-compose.yml 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: docker-compose.yml docker-compose.yml 1. Database Service ( db ) 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" 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. 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. image: postgres:14.2-alpine container_name: demo-postgres-14.2: Names the container demo-postgres-14.2. This name is used to reference the container in commands and logs. container_name: demo-postgres-14.2 : Names the container demo-postgres-14.2 . This name is used to reference the container in commands and logs. container_name: demo-postgres-14.2 demo-postgres-14.2 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. 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. 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. 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. 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. postgres_data:/var/lib/postgresql/data: postgres_data /var/lib/postgresql/data 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. command: "postgres -c 'max_connections=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. 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. 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. POSTGRES_DB: ${POSTGRES_DB}: Sets the name of the default database to create, using an environment variable POSTGRES_DB. POSTGRES_DB: ${POSTGRES_DB} : Sets the name of the default database to create, using an environment variable POSTGRES_DB . POSTGRES_DB: ${POSTGRES_DB} POSTGRES_DB POSTGRES_USER: ${POSTGRES_USER}: Sets the default username for accessing the PostgreSQL database, using the POSTGRES_USER environment variable. POSTGRES_USER: ${POSTGRES_USER} : Sets the default username for accessing the PostgreSQL database, using the POSTGRES_USER environment variable. POSTGRES_USER: ${POSTGRES_USER} POSTGRES_USER POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}: Sets the password for the default user, using the POSTGRES_PASSWORD environment variable. POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} : Sets the password for the default user, using the POSTGRES_PASSWORD environment variable. POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PASSWORD 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. 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. "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. "5432:5432" 2. Web Application Service ( demo-web ) 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" 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 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. Volumes codevolumes: postgres_data: app-storage: postgres_data: Defines a named volume postgres_data used by the db service to persist PostgreSQL data. app-storage: Defines a named volume app-storage used by the demo-web service to persist application-specific data, such as uploads and logs. 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). build: 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). 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. context: . . 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). 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). 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). 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 ). RAILS_ENV=${RAILS_ENV} RAILS_ENV development test 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. 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. command: "./bin/rails server -b 0.0.0.0" 0.0.0.0 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. environment: 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. RAILS_ENV=${RAILS_ENV}: Sets the Rails environment inside the container using the RAILS_ENV environment variable. RAILS_ENV=${RAILS_ENV} : Sets the Rails environment inside the container using the RAILS_ENV environment variable. RAILS_ENV=${RAILS_ENV} RAILS_ENV POSTGRES_HOST=${POSTGRES_HOST}: Sets the PostgreSQL host address. POSTGRES_HOST=${POSTGRES_HOST} : Sets the PostgreSQL host address. POSTGRES_HOST=${POSTGRES_HOST} POSTGRES_DB=${POSTGRES_DB}: Sets the database name. POSTGRES_DB=${POSTGRES_DB} : Sets the database name. POSTGRES_DB=${POSTGRES_DB} POSTGRES_USER=${POSTGRES_USER}: Sets the PostgreSQL user. POSTGRES_USER=${POSTGRES_USER} : Sets the PostgreSQL user. POSTGRES_USER=${POSTGRES_USER} POSTGRES_PASSWORD=${POSTGRES_PASSWORD}: Sets the PostgreSQL user password. POSTGRES_PASSWORD=${POSTGRES_PASSWORD} : Sets the PostgreSQL user password. POSTGRES_PASSWORD=${POSTGRES_PASSWORD} RAILS_MASTER_KEY=${RAILS_MASTER_KEY}: Sets the Rails master key, which is used for encrypting credentials and other secrets. RAILS_MASTER_KEY=${RAILS_MASTER_KEY} : Sets the Rails master key, which is used for encrypting credentials and other secrets. RAILS_MASTER_KEY=${RAILS_MASTER_KEY} 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. volumes : 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. .:/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. .:/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. .:/rails docker-compose.yml /rails 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. 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. app-storage:/rails/storage app-storage /rails/storage 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. depends_on : 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. 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. db demo-web db 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 volume postgres_data used by the db service to persist PostgreSQL data. app-storage: Defines a named volume app-storage used by the demo-web service to persist application-specific data, such as uploads and logs. ports: 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. "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. "3000:3000" Volumes codevolumes: postgres_data: app-storage: codevolumes: postgres_data: app-storage: postgres_data: Defines a named volume postgres_data used by the db service to persist PostgreSQL data. app-storage: Defines a named volume app-storage used by the demo-web service to persist application-specific data, such as uploads and logs. postgres_data : Defines a named volume postgres_data used by the db service to persist PostgreSQL data. postgres_data postgres_data db app-storage : Defines a named volume app-storage used by the demo-web service to persist application-specific data, such as uploads and logs. app-storage app-storage demo-web bin/docker-entrypoint bin/docker-entrypoint 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: bin/docker-entrypoint bin/docker-entrypoint Shebang and Exit on Error bashCopy code#!/bin/bash set -e bashCopy code#!/bin/bash set -e #!/bin/bash: This line specifies that the script should be run using the Bash shell. #!/bin/bash : This line specifies that the script should be run using the Bash shell. #!/bin/bash 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. 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. set -e Conditional Database Creation or Migration 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 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. 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. if [ "${*}" == "./bin/rails server" ]; then ... fi "${*}" ./bin/rails server * ./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 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. ./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 ./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 ). rails db:create 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 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. ./bin/rails db ./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 . rails db:prepare rails db:create rails db:migrate Executing the Main Process bashCopy codeexec "${@}" 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. 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. exec "${@}" @ ./bin/rails server ./bin/rails server Conclusion 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. Dockerfile 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 Dockerfile docker-compose.yml 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"] 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: 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 "${@}" #!/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 "${@}"