Docker and Docker Compose
Docker is a tool that allows you to package and deploy applications in a standardized way, using containers. Docker Compose is a tool that will enable you to define and run multi-container Docker applications, using a single configuration file. Basically docker composer group docker containers into a manageable stack of services. It can be especially useful for development, as it allows you to quickly spin up a consistent environment for your app, without having to manually install and configure these dependencies on your local machine.
Dockerize Rails App
Create a file named
Dockerfile.dev in the root of your application. This file will be used to build a Docker image of your application. Add the following contents to the
FROM ruby:2.7.1 # Install dependencies RUN apt-get update -qq && apt-get install -y nodejs postgresql-client # Set an environment variable to store the application's directory ENV APP_HOME /myapp # Create the application's directory and set it as the working directory RUN mkdir $APP_HOME WORKDIR $APP_HOME # Copy the Gemfile and Gemfile.lock into the working directory COPY Gemfile Gemfile.lock $APP_HOME/ # Install the gems RUN bundle install # Copy the application code into the working directory COPY . $APP_HOME # Expose the default Rails server port EXPOSE 3000 # Run the Rails server when the container starts CMD ["rails", "server", "-b", "0.0.0.0"]
This Dockerfile does the following:
It specifies that we want to use the
ruby:2.7.1image as the base for our Docker image. This image comes with the Ruby runtime and other necessary dependencies pre-installed.
It sets the working directory to
/app, which is where we will be storing our Rails code.
It copies the
Gemfile.lockfiles from the host machine into the Docker container.
It installs the dependencies specified in the
It copies the rest of the code from the host machine into the Docker container.
It exposes the default Rails port, which is 3000.
It sets the default command to start the Rails server, which is
bundle exec rails server -b 0.0.0.0.
A Dockerfile is a text file that contains instructions for building a Docker image. It is used to automate the process of building an image, allowing you to specify the base image, dependencies, and other details needed to create a custom image for your application. It is not necessarily required to have a different Dockerfile for each environment, but it is common to customize the Dockerfile depending on the environment in which the image will be used. For example, you might use a different base image depending on the environment, or install different dependencies or configure different environment variables.
Use Docker Compose to Manage Docker Containers
In the context of developing Rails app, Docker Compose can be used to set up and run all of the necessary dependencies for your application in a single command
Next create a docker-compose.yml file in the root of your project, which will contain the configuration for your application’s containers.
Here, we will have a sample rails app
myapp which have a database (Postgres), a message queue (RabbitMQ), and a cache (Redis) as dependencies.
version: '3' services: db: image: postgres environment: POSTGRES_USER: myapp POSTGRES_PASSWORD: mypass redis: image: redis rabbitmq: image: rabbitmq web: build: context: ./ dockerfile: Dockerfile.dev command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - db - redis - rabbitmq
In this configuration, we have defined five services (containers):
db: a PostgreSQL database
redis: a Redis server
rabbitmq: a RabbitMQ message broker
web: our Rails app
db service is running a Postgres container, and we have specified some environment variables to set the username and password for the Postgres user. The
rabbitmq services are running the official Redis and RabbitMQ images, respectively. Finally, the
web service is building a container from the current directory (i.e. the root of the Rails app), and it is depending on the
rabbitmq services to be up and running before it starts.
By default, the data stored in a Docker container is not persisted when the container is stopped or removed. This means that if you stop or remove the
rabbitmq containers, any data stored in them will be lost.
To persist the data, you can use Docker volumes to store the data outside of the containers. This will allow the data to survive even if the containers are stopped or removed.
To add a volume to a service in Docker Compose, you can use the
volumes key in the service’s configuration. For example, to add a volume for the
db service, you could use the following configuration:
services: db: volumes: - pgdata:/var/lib/postgresql/data volumes: pgdata:
This will create a volume named
pgdata and mount it at
/var/lib/postgresql/data inside the
db container. Any data stored in this location will be persisted to the volume, even if the
db container is stopped or removed.
You can use a similar approach to add volumes for the
rabbitmq services. Just make sure to mount the volumes at the appropriate locations for each service.
services: redis: volumes: - redisdata:/data rabbitmq: volumes: - rabbitmqdata:/var/lib/rabbitmq volumes: redisdata: rabbitmqdata:
With these volumes in place, the data stored in your
rabbitmq containers will be persisted even if the containers are stopped or removed.
In Docker, you can use network isolation to create separate networks for your containers. This can be useful if you want to create a separate network for your Rails app and its dependencies.
To create a separate network for your containers, you can use the
networks key in your
docker-compose.yml file. For example, to create a network named
myapp_network and add all of your services to it, you could use the following configuration:
services: db: networks: - myapp_network redis: networks: - myapp_network rabbitmq: networks: - myapp_network web: networks: - myapp_network networks: myapp_network:
docker-compose.yml may look like below
version: '3' services: db: image: postgres:12 environment: POSTGRES_USER: myapp POSTGRES_PASSWORD: mypass ports: - 5432:5432 volumes: - db_data:/var/lib/postgresql/data networks: - myapp_network redis: image: redis volumes: - redisdata:/data rabbitmq: image: rabbitmq:3.8.9-management ports: - "5672:5672" - "15672:15672" volumes: - rabbitmq_data:/var/lib/rabbitmq networks: - myapp_network web: build: context: . dockerfile: Dockerfile.dev command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - myapp_data:/myapp/data ports: - "3000:3000" networks: - myapp_network depends_on: - db - redis - rabbitmq volumes: db_data: myapp_data: rabbitmq_data: redisdata: networks: myapp_network:
Once you have created your
docker-compose.yml file, you can use the following command to start all of the containers for your application:
sh docker-compose up
This will build and start all of the containers defined in your
docker-compose.yml file. You should now be able to access your Rails app at
Here are some basic Docker Compose commands that you may find useful:
docker-compose up: This command starts the services defined in the
docker-compose.ymlfile. By default, it creates and starts all the services in the background (detached mode). You can use the
-dflag to start the services in detached mode, or the
-fflag to specify a different Compose file.
docker-compose down: This command stops and removes all the containers, networks, and volumes created by
docker-compose up. You can use the
-vflag to remove the volumes as well.
docker-compose build: This command builds the images for the services defined in the
docker-compose.ymlfile. You can use the
--no-cacheflag to build the images from scratch, rather than using the cache.
docker-compose run: This command allows you to run a command in a running container. For example,
docker-compose run web rake db:createwill open a Bash shell in the
webcontainer and run
docker-compose logs: This command displays the logs for a service. You can use the
-fflag to follow the logs, and the
--tailflag to show the last N lines of the logs.
These are just a few of the basic Docker Compose commands that you may find useful. For a complete list of commands and options, you can refer to the Docker Compose documentation.
Optional UI for Managing Containers
Ideally, you should use
docker-compose.yml to configure each containers however there several tools that provide a graphical user interface (UI) for managing Docker containers and Docker Compose projects.
One such tool is Portainer, which is an open-source management UI for Docker. With Portainer, you can easily view and manage your containers, images, and Docker Compose projects from a web-based UI.
To use Portainer, you will need to install it as a Docker container. You can do this by running the following command:
docker run -d -p 9000:9000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer
This will start the Portainer container and expose it on port 9000. You can then access the Portainer UI by visiting
http://localhost:9000 in your web browser.
Once you have Portainer set up, you can use it to start, stop, and manage your Docker containers and Docker Compose projects. You can also use it to view logs, inspect containers, and perform other tasks.
Other tools that provide a UI for managing Docker containers and Docker Compose projects include Docker Desktop, Kitematic, and Shipyard. You can choose the tool that best fits your needs and workflow.
PROS/CONS of Dockerizing a Rails app:
Consistency across environments: By using Docker to containerize your application, you can ensure that your development, staging, and production environments are all running the same version of the application and its dependencies. This can help prevent issues caused by differences between environments and make it easier to debug problems when they do arise.
Ease of deployment: With Docker, you can package your entire application and its dependencies into a single container image. This makes it easy to deploy your application to any environment that supports Docker, without worrying about dependencies or configuration issues.
Isolation of dependencies: By running your application and its dependencies in separate containers, you can ensure that they are isolated from each other and from the host system. This can help prevent conflicts between dependencies and make it easier to upgrade or change individual components.
Scalability: With Docker, it is easy to scale your application by running additional copies of your application container. This can be useful if you need to handle a large amount of traffic or if you want to run multiple copies of your application for high availability.
Ease of maintenance: By using Docker, you can easily update or upgrade your application and its dependencies by creating new container images. This makes it easy to apply security patches and other updates without affecting the underlying host system.
Additional complexity: Dockerizing an application can add an additional layer of complexity to your development and deployment process. You may need to learn new tools and concepts, and you may need to spend more time configuring and maintaining your Docker containers and infrastructure.
Increased resource usage: Running an application in a Docker container can require more resources than running it directly on the host system. This is because each container has its own operating system and requires its own resources, such as memory and CPU. If you have a resource-constrained environment, this may be a consideration.
Performance overhead: Running an application in a Docker container can introduce some performance overhead, as the application’s requests and responses must pass through the container’s virtualized environment. In most cases, this overhead is minimal and should not be noticeable, but it is worth considering if you have very high performance requirements.
Limitations on host system access: When running an application in a Docker container, it may not have direct access to certain resources or features on the host system. For example, you may need to use Docker volumes or other workarounds to access host system files or devices.
While dockerizing a Rails app can bring many benefits, it is important to weigh the potential disadvantages against your specific needs and goals. In some cases, the benefits of Docker may outweigh the additional complexity, while in other cases it may not be the best fit.
Here are some of the cases where benefits may outweigh its complexity:
Developing and deploying applications in different environments: As a developer, I think this is th biggest benefits on dockerizing app. If you need to develop and deploy your application in different environments, such as local development, staging, and production, using Docker can help ensure that your application is consistent across all environments. This can make it easier to debug issues and reduce the risk of problems caused by differences between environments.
Managing dependencies: If your application has complex dependencies or requires specific versions of libraries or frameworks, using Docker can make it easier to manage these dependencies. By containerizing your application, you can ensure that it always has the correct dependencies and that they are isolated from the host system.
Scaling applications: If you need to scale your application to handle a large amount of traffic or to ensure high availability, using Docker can make it easier to run multiple copies of your application and manage them as a group.
Maintaining applications: If you need to frequently update or upgrade your application or its dependencies, using Docker can make it easier to create new container images and roll out updates without affecting the underlying host system.
Overall, if your application has complex dependencies, needs to run in different environments, or needs to be scalable or maintainable, the benefits of Docker may outweigh the additional complexity. It is important to carefully consider your specific needs and goals when deciding whether to dockerize your application.
You can checkout sample rails app created on this repo.