39

I'm building an app using multiple dockerfiles (one for each service). My app's directory structure is as follows:

app
├── dockerfiles
│   ├── webserver
│   │   └── Dockerfile
│   └── database
│       └── Dockerfile
├── public
    └── <frontend>
├── db
    └── <data>
  [...]
├── LICENSE
├── README.md
└── docker-compose.yml

In my webserver's Dockerfile, I want to copy in my existing code using the COPY command:

# Dockerfile
COPY ./public /var/www/html

And I want to deploy the app using my docker-compose.yml file:

# docker-compose.yml
version: "3"
services:
   webserver:
      build: ./dockerfiles/webserver
      image: webserver:php-apache

However, when I run docker-compose from the working directory (app), I get the following error:

Building webserver
Step 1/2 : FROM php:7.1.11-apache-jessie
 ---> cb6a5015ad72
Step 2/2 : COPY ./public /var/www/html
Service 'webserver' failed to build: COPY failed: stat /var/lib/docker/tmp/docker-builder193736188/public: no such file or directory

This error disappears if I move my webserver's Dockerfile to the app's root, so I know that it's being caused by a paths or build context issue.

And knowing this, we can fix the problem one of two ways, by either:

(1) Using one Dockerfile for the entire app (in the app's root), or

app
└── Dockerfile

(2) Using multiple Dockerfiles for each service (in the app's root).

app
├── Dockerfile.webserver
└── Dockerfile.database

These solutions are bad because using one dockerfile/container for everything is not best practice (1), and having multiple dockerfiles organized in this way just looks messy (2).


So, My Question is:

How do we fix this problem without changing our original directory structure?

  • What changes need to be made to the dockerfiles, to docker-compose.yml, or to the basic runtime commands?
  • Is there a better way to organize everything?
  • What about the WORKDIR command?

Ideally, the best solution should work for both dev (local) and production (remote) environments, so let's avoid volumes for now...

CodyEakins
  • 602
  • 1
  • 7
  • 16

4 Answers4

40

All you need to do here is add context: . and dockerfile in your build section inside your docker-compose.yml file so that your service understands the complete directory structure.

# docker-compose.yml
version: "3"
services:
  webserver:
    build:
      context: .
      dockerfile: ./dockerfiles/webserver/Dockerfile
    image: webserver:php-apache
vivekyad4v
  • 13,321
  • 4
  • 55
  • 63
  • 1
    This worked perfectly! I knew it had to be an issue with the build context, but I had so much trouble looking for a solution... Hopefully this will help someone in a similar situation in the near future! – CodyEakins Dec 13 '17 at 05:54
21

Not exactly OP's scenario, but if you're not using docker-compose, you can also specify context and Dockerfile separately:

docker build . -f app/dockerfiles/webserver/Dockerfile

This builds with the current directory . as the context, but uses the Dockerfile from the subdirectory.

markfickett
  • 552
  • 3
  • 11
6

As already clarified elsewhere a combination of context and dockerfile does the trick. I only want to add that you don't even need to locate the docker-compose.yml file itself in your application's root directory for that to work.

For example, in one of my projects I have multiple compose configurations to support several environments (development, staging, production, etc.) and I decided to put everything in their own subdirectories, for cleanliness. My directory tree is as follows:

.
+-- README.md
+-- scripts
|   +-- docker
|       +-- compose.yml
|       +-- compose-stage.yml
|       +-- compose-prod.yml
|       +-- dockerfile
|       +-- ...
|   +-- ...

where compose.yml would be as follows:

# compose.yml
version: "3.9"
services:
  web:
    build:
      context: ../../
      dockerfile: ./scripts/docker/dockerfile
....

Note the context, which points to the application's root directory - two levels up from the directory the compose.yml file is located into - and the dockerfile, which is relative to the context.

With the above directory tree and compose.yml file, all that we have to do is running the following command from the application's root directory (the directory where README.md is located, that is):

> docker compose -f scripts/docker/compose.yml up

and everything works like charm!

I know sounds a bit convoluted, and probably it is, but the time spent will be repaid with interests when the time comes to deal with the deployment of the application to three (or more!) different environments.

Sal Borrelli
  • 2,201
  • 19
  • 19
1

The scope for the COPY instruction is the folder where Dockerfile is. My suggestion is to change your Dockerfile to public folder and change the instruction to COPY . /var/www/html.

J. Ordaz
  • 415
  • 6
  • 16