Docker is a handy tool for containerization. It's so useful that sometimes, we want to have more than one Dockerfile in the project. Unfortunately, this goes against the straightforward convention of naming all Dockerfiles just “Dockerfile”.
In this tutorial, we'll look at working around it and maintaining a clean project structure.
Dockerfile is a file that contains all the instructions needed for assembling the Docker image. Docker can automatically build images using it, without the need for any additional commands or parameters. Because of the naming convention, we don't even need to (and until version 1.8.0, we actually couldn't) specify the path for the file.
We can call Docker's build command from the directory in which the Dockerfile is located:
$ docker build .
3. Specifying the Dockerfile Name
From version 1.8.0, we can change the Dockerfile's name and pass it to the build command using “-f” parameter. Let's say we have two Dockerfiles, one for building the backend and another for building the frontend.
We can name them appropriately and invoke the build command two times, each time passing the name of one of the Dockerfiles:
$ docker build -f Dockerfile.frontend . ... $ docker build -f Dockerfile.backend .
This solution will work fine but has some inconvenient drawbacks. The first one is that we need to invoke the build command separately for each file. This isn't horrible for two files, but if we had more components, it could quickly create the need for additional build scripts. The second disadvantage is that some IDEs can be thrown off track because of the deviation from the convention and stop providing syntax highlighting.
4. Using docker-compose
Instead of changing the names of Dockerfiles, we can place them in separate folders. Then, we can use docker-compose to trigger the build of all of them. Let's say we have a directory structure like:
docker-compose.yml docker ├── frontend │ └── Dockerfile └── backend └── Dockerfile
While the most basic usage of the docker-compose file usually means using images from a repository, we can also provide directory paths for build:
version: '3' services: frontend: build: ./docker/frontend ports: - "8081:8081" backend: build: ./docker/backend ports: - "8080:8080"
Now running docker-compose will build images from Dockerfiles:
$ docker-compose up
Moreover, in one docker-compose file, we can put services with different ways of creating an image. Some can be built on the fly from our source, others can be provided by an external registry. For example, we may want to build the backend image but get the database image from the public registry:
services: backend: build: ./docker/backend ports: - "8080:8080" postgres: image: 'postgres:latest'
Now, docker-compose will not only build our services and run them, but also provide a database for us.
In this tutorial, we learned two different strategies for dealing with multiple Dockerfiles in one project.
The first one is based on changing the names of different Dockerfiles and provides a quick and elegant solution for the problem. The second one relies on docker-compose and gives additional options for structuring and automating the building process.