Rails on Docker: How to Share Containers Across Multiple Projects
When running your applications in containers, you usually want to isolate everything so that the app can be independent of any external . There are some cases, though, where it might be desirable to share a single container across multiple projects.
For example, when simulating a micro-services architecture in development, you might want to share a single database container across two or more applications, so that they can both access the same data.
Docker and Docker Compose make this possible through the use of Docker networks, allowing containers from different compose projects to be attached to the same network.
Sharing Containers in Compose Files
Suppose we have two entirely separate Rails applications that need to connect to a single database container. We can achieve this by using two docker-compose files, and specifying a single network to which our containers will be connected.
→ Create a configuration file,
# docker-compose.1.yml version: '3.2' services: db: image: postgres environment: - POSTGRES_DB=my_app_development - POSTGRES_PASSWORD=password web_one: image: cblunt/rails-basic-app depends_on: - db ports: - '3000:3000' environment: - RAILS_ENV=development - DB_HOST=db - DB_USER=postgres - DB_PASSWORD=password - DB_NAME=my_app_development command: /bin/sh -c 'bin/rails db:migrate && bin/rails s -b 0.0.0.0 -P 3000'
→ Next, create a second configuration file,
docker-compose.2.yml, in the same folder.
This configuration file will declare our ‘second’ web application (in this case, just a second instance of the same application).
Note that we only need to declare the web application itself in this configuration. We’ll also expose
web_two on port 3001, to prevent port clashes on our network:
# docker-compose.2.yml version: '3.2' services: web_two: image: cblunt/rails-basic-app ports: - '3001:3001' environment: - RAILS_ENV=development - DB_HOST=db - DB_USER=postgres - DB_PASSWORD=password - DB_NAME=my_app_development command: /bin/sh -c 'bin/rails s -b 0.0.0.0 -p 3001'
docker-compose up on the two files. As they are in the same directory, Docker Compose will create a single default network, and attach all the containers to that network:
docker-compose -f docker-compose.1.yml -f docker-compose.2.yml up -d Creating network "project_default" with the default driver Creating project_db_1 ... done Creating project_web_two_1 ... done Creating project_web_one_1 ... done
In your browser, open the first application (running on port 3000). If you’ve used my example image above, open http://localhost:3000/posts. Use the simple scaffold to create a new blog post entry:
Once you have added a post, visit the second instance of the application (running on port 3001). You will see that the post you added shows up here as well, so both our applications are talking to the same database:
Sharing Containers Across Different Projects
So far, everything has worked pretty much as expected when your compose files are in the same project folder, but what if you have two compose files in entirely separate projects that need to share a common container?
For example, lets assume you have a project with the following folder structure:
+ project_one/ | - docker-compose.yml | + project_two/ | - docker-compose.yml
As before, we can use Compose’s ability to load multiple configuration files to bundle everything into one network:
docker-compose -f project_one/docker-compose.yml -f project_two/docker-compose.yml up -d
Again, Compose will create a single default network, and attach all the containers to that network.
Although this works as before, if dealing with multiple projects, I’d prefer to explicitly declare to which network the containers are attached. This helps to prevent any confusion when debugging our configuration.
You can declare a default network using the
networks entry in your
# project_one/docker-compose.yml version: '3.2' networks: default: external: name: my_net services: # ...
# project_two/docker-compose.yml version: '3.2' networks: default: external: name: my_net services: # ...
Note that when declaring an external network manually, Compose will not automatically create it. Therefore, we need to create the network manually using
docker network first:
docker network create my_net
Now we can use the compose configurations independently, knowing that any containers declared within them will be attached to the same network:
docker-compose -f project_one/docker-compose.yml up -d docker-compose -f project_two/docker-compose.yml up -d
As before, both
web containers will have access the same database.
Docker’s networking allows you to build complex micro-service architectures, and share container resources with multiple applications. Docker Compose is great for simulating this type of set up in development.
Once you start running your apps in production, you can take advantage of more powerful orchestration tools, such as Docker Swarm, to configure and manage your applications and their environment.