I am following the official tutorial on the Docker website
The docker file is
FROM python:3
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt /code/
RUN pip install -r requirements.txt
COPY . /code/
The docker-compose is:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
I do not understand why in the Dockerfile they are copying the code COPY . /code/
, but then again mounting it in the docker-compose - .:/code
? Is it not enough if I either copy or mount?
3
Answers
It used for saving the image after with the code.
When you use COPY it save it as part of the image.
While mounting is only while developing.
Both the
volumes:
andcommand:
in thedocker-compose.yml
file are unnecessary and should be removed. The code and the defaultCMD
to run should be included in the Dockerfile.When you’re setting up the Docker environment, imagine that you’re handed root access to a brand-new virtual machine with nothing installed on it but Docker. The ideal case is being able to
docker run your-image
, as a single command, pulling it from some registry, with as few additional options as possible. When you run the image you shouldn’t need to separately supply its source code or the command to run, these should usually be built into the image.In most cases you should be able to build a Compose setup with fairly few options. Each service needs an
image:
orbuild:
(both, if you’re planning to push the image), oftenenvironment:
,ports:
, anddepends_on:
(being aware of the limitations of the latter option), and your database container(s) will needvolumes:
for their persistent state. That’s usually it.The one case where you do need to override
command:
in Compose is if you need to run a separate command on the same image and code base. In a Python context this often comes up to run a Celery worker next to a Django application.Here’s a complete example, with a database-backed Web application with an async worker. The Redis cache layer does not have persistence and no files are stored locally in any containers, except for the database storage. The one thing missing is the setup for the database credentials, which requires additional
environment:
variables. Note the lack ofvolumes:
for code, the singlecommand:
override where required, andenvironment:
variables providing host names.Where you do see
volumes:
overwriting the image’s code like this, it’s usually an attempt to avoid needing to rebuild the image when the code changes. In the Dockerfile you show, though, the rebuild is almost free assuming therequirements.txt
file hasn’t changed. It’s also almost always possible to do your day-to-day development outside of Docker – for Python, in a virtual environment – and use the container setup for integration testing and deployment, and this will generally be easier than convincing your IDE that the language interpreter it needs is in a container.Sometimes the Dockerfile does additional setup (changing line endings or permissions, rearranging files) and the
volumes:
mount will hide this. It means you’re never actually running the code built into the image in development, so if the image setup is buggy in some way you won’t see it. In short, it reintroduces the "works on my machine" problem that Docker generally tries to avoid.Ideally we use a single
Dockerfile
to create the image we use for both production and development. This increases the similarity of the app’s runtime environment, which is a good thing.In contrast to what @David writes: it’s quite handy to do your day-to-day development with a Docker container. Your code runs in the same environment in production and development. If you use
virtualenv
in development you’re not making use of that very practical attribute of Docker. The environments can diverge without you knowing and prod can break while dev keeps on working.So how do we let a single
Dockerfile
produce an image that we can run in production and use during development? First we should talk about the code we want to run. In production we want to have a specific collection of code to run (most likely the code at a specific commit in your repository). But during development we constantly change the code we want to run, by checking out different branches or editing files. So how do we satisfy both requirements?We instruct the
Dockerfile
to copy some directory of code (in your example.
) into the image, in your example/code
. If we don’t do anything else: that will be the code that runs. This happens in production.But in development we can override the
/code
directory with a directory on the host computer using a volume. In the example the Docker Compose file sets the volume. Then we can easily change the code running in the dev container without needing to rebuild the image.Also: even if rebuilding is fast, letting a Python process restart with new files is a lot faster.