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:

# 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'

Now run 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:

Add a Post to App One

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:

Add a post to App Two

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 docker-compose.yml files:

# 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.

Next Steps

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.