Is it possible to to mount a host directory in to a container but only allowing overwriting files that exist on the host?
Example github repo: https://github.com/UniBen/stackoverflow-59031249
E.g
Host:
src/
public/
index.php (200kb)
Container:
src/
public/
index.php (100kb)
vendor/
...
Desired output: (The container file system merged with the mounted host system files which exists. Note the size of the index.php file.)
src/
public/
index.php (200kb)
vendor/
...
Actual output:
src/
public/
index.php (200kb)
Example docker-compose.yml
version: '3.2'
services:
php:
image: php
volumes:
- ./src:/src
Edit: So it looks like overlayfs implemented by docker is only used for bulding docker images and can not be used for volumes what so ever. I still think it's possible to specify a custom driver but not sure how. As a temp fix I've done some fancy stuff with mapping stuff out of container, diffing it and putting it back in but not ideal.
Is it possible to to mount a host directory in to a container but ...
No. The only Docker mounting option is a straight-up "replace this directory in the container with the equivalent directory from the host". There is no way to modify this, selectively hide subdirectories, or implement your "only if it already exists" rule.
In the example you're showing, you probably don't want a volume at all. Files like index.php and a vendor directory are application source code, and in typical use you'd write a Dockerfile, COPY index.php . to move the file into the image, and then RUN composer ... to create the vendor tree. This would be isolated from your host environment, so the vendor directory in the image would be separate from whatever existed on your host system.
Actually, there are no options to control such behavior, e.g, how data between source and dest will be handled. But if David's answer is not really your case you could do something like this:
version: '3.2'
services:
example:
build:
context: .
volumes:
- data:/src
- ./src:/src/host
volumes:
data:
How docker documentation says:
If you start a container which creates a new volume, and the container
has files or directories in the directory to be mounted the
directory’s contents are copied into the volume.
So after that, let's investigate a little bit:
/src # ls
file.a.txt file.c.txt host
/src # cat host/file.a.txt
host
/src # cat file.a.txt
container
/src # cat file.c.txt
container
Data from from container is saved into data named volume. The data from the host machine live in the host folder. Now you can copy from the host folder to the src with cp or rsync with any rules that you want.
This is a quite fishy and artificial example, maybe it's a good idea to rethink current implementation.
Related
I am checking the docker documentation on how to use named volumes to share data between containers.
In Populate a volume using a container it is specified that:
If you start a container which creates a new volume, as above, and the container has files or directories in the directory to be mounted (such as /app/ above), the directory’s contents are copied into the volume. The container then mounts and uses the volume, and other containers which use the volume also have access to the pre-populated content.
So I did a simple example where:
I start a container which creates the volume and mounts it to a directory with existing files
I start a second container on which I mount the volume and indeed I can see the first container's files.
So far so good.
However I wanted to see if it is possible to have pre-populated content from more than one containers.
What I did was
Create two simple images which have their respective configuration files in the same directory
FROM alpine:latest
WORKDIR /opt/test
RUN mkdir -p "/opt/test/conf" && \
echo "container from image 1" > /opt/test/conf/config_1.cfg
FROM alpine:latest
WORKDIR /opt/test
RUN mkdir -p "/opt/test/conf" && \
echo "container from image 2" > /opt/test/conf/config_2.cfg
Create a docker compose which defines a named volume which is mounted on both services
services:
test_container_1:
image:
test_image_1
volumes:
- test_volume:/opt/test/conf
tty: true
test_container_2:
image:
test_image_2
volumes:
- test_volume:/opt/test/conf
tty: true
volumes:
test_volume:
Started the services.
> docker-compose -p example up
Creating network "example_default" with the default driver
Creating volume "example_test_volume" with default driver
Creating example_test_container_2_1 ... done
Creating example_test_container_1_1 ... done
Attaching to example_test_container_1_1, example_test_container_2_1
According to the logs container_2 was created first and it pre-populated the volume. However, the volume was then mounted to container_1 and the only file available on the mount was apparently /opt/test/conf/config_2.cfg effectively removing config_1.
So my question is, if it is possible to have a volume populated with data from 2 or more containers.
The reason I want to explore this, is so that I can have additional app configuration loaded from different containers, to support a multi tenant scenario, without having to rework the app to read the tenant configuration from different folders.
Thank you in advance
Once there is any content in a named volume at all, Docker will never automatically copy content into it. It will not merge content from two different images, update the volume if one of the images changes, or anything else.
I'd advise you to ignore the paragraph you quote in the Docker documentation. Assume any volume you mount into the container is initially empty. This matches the behavior you'll get with Docker bind-mounts (host directories), Kubernetes persistent volumes, and basically any other kind of storage besides Docker named volumes proper. Don't mount a volume over the content in your image.
If you can, restructure your application to avoid sharing files at all. One common use of named volumes I see is trying to republish static assets to a reverse proxy, for example; rather than trying to use a named volume (which will never update itself) you can COPY the static assets into a dedicated Web server image. This avoids the various complexities around trying to use a volume here.
If you really don't have a choice in the matter, then you can approach this with dedicated code in both of the containers. The basic setup here is:
Have a data directory somewhere outside your application directory, and mount the volume there.
Include the original files in the image somewhere different.
In an entrypoint wrapper script, copy the original files into the data directory (the mounted volume).
Let's say for the sake of argument that you've installed the application into /opt/test, and the data directory will be /etc/test. The entrypoint wrapper script can be as little as
#!/bin/sh
# Copy config files from the application tree into the config tree
# (overwriting anything that's already there)
cp /opt/test/* "$TEST_CONFIG_DIR"
# Run the main container command
exec "$#"
In the Dockerfile, you need to make sure that directory exists (and if you'll use a non-root user, that user needs permission to write to it).
FROM alpine
WORKDIR /opt/test
COPY ./ ./
ENV TEST_CONFIG_DIR=/etc/test
RUN mkdir "$TEST_CONFIG_DIR"
ENTRYPOINT ["./entrypoint.sh"]
CMD ["./my_app"]
Finally, in the Compose setup, mount the volume on that data directory (you can't use the environment variable, but consider the filesystem path part of the image's API):
version: '3.8'
volumes:
test_config:
services:
one:
build: ./one
volumes:
- test_config:/etc/test
two:
build: ./two
volumes:
- test_config:/etc/test
You would be able to run, for example,
docker-compose run one ls /etc/test
docker-compose run two ls /etc/test
to see both sets of files appear there.
The entrypoint script is code you control. There's nothing especially magical about it beyond the final exec "$#" line to run the main container command. If you want to ignore files that already exist, for example, or if you have a way to merge in changes, then you can implement something more clever than a simple cp command.
I have a simple .dockerignore file with the following:
.git/
.idea/
venv/
My docker-compose.yml file mounts the volume:
version: "3"
services:
frontend:
build: .
command: ["gunicorn", "--bind", "0.0.0.0:8000", "project.app:create_app()"]
env_file:
- .env
volumes:
- .:/frontend
ports:
- "8000:8000"
Perhaps I don't understand the full syntax or intent of the .dockerignore file, but after running docker-compose up --build, .git/, .idea/ and venv/ end up in my container.
I've read up and saw this but it doesn't seem feasible that one cannot mount and prevent files and directories from landing in the container.
How do I prevent these directories from becoming available in the container?
The .dockerignore file will modify the context that is sent to the docker daemon to build the image. So the created image will not have these files inside unless you recreate them.
The volume mount is completely separate and a host volume mount, aka bind mount, will map the contents of the host directory directly into the container as is. This is a Linux OS level activity that doesn't follow .dockerignore.
You will need to exclude those files from the directory that you mount into the container, or not mount the volume into the container and rely on the image to take advantage of .dockerignore.
I am trying to develop a project locally using Docker Compose and to prevent re-building my image on every update, I've added a bind-mount that maps my src directory to my WORDIR in Docker. All changes made on my local machine are then reflected in my Docker container...EXCEPT for one file. For some reason, there's a single file in my project, that when I change its contents, the change is not reflected in the Docker container even though other files adjacent to this file DO detect file changes. Which leads me to believe that the directory is mapped correctly but it's some other issue with the file itself?
docker-compose.yaml
graphql:
build:
context: .
dockerfile: ./app/graphql/src/Dockerfile
target: development
volumes:
- ./app/graphql/src:/workspace
- /workspace/node_modules/
Dockerfile
# ------------> Base Image
FROM node:14 AS base
WORKDIR /workspace
COPY ./app/graphql/src .
# ------------> Development Image
FROM base AS development
CMD ["npm", "run", "dev"]
I haven't figured out how to show directory structure but the files that I am modifying are located in:
/app/graphql/src/api/graphql
Where file a.ts detects changes and is reflected in the Docker container but b.ts does not. I read about how Docker depends on the inode of the file to match if bind mounting specific files. I'm mounting a directory, but for a sanity check, I ran:
ls -i
in both the host and container and confirmed that the inodes matched.
I have two M1 Mac computers and I confirmed that this is a problem between both machines.
Any additional thoughts to debug this problem? My only other thought is that I hit a max number of files that can be tracked, but that's why I removed the node_modules. Any assistance would be really helpful!
EDIT: I created a new file, c.ts and duplicated the contents of b.ts (the file that wasn't changing between host and container)...and c.ts detects changes properly! Is there a way to inspect why a certain file isn't broadcasting changes? This is so strange.
You should remove COPY ./app/graphql/src . directive from your Dockerfile because this folder will mounted to container as volume.
I have a custom image created from my own project. The Dockerfile is quite simple:
from wordpress
COPY test.html /var/www/html
Using docker composer, if I run it using a volume, it's works fine.
wordpress:
...
image: my_project_image
volumes: ['volumetest:/var/www/html']
...
volumes:
volumetest:
But if instead of creating a volume, if I map a local folder to the remote folder, the file test.html is not created, neither inside the wordpress container, neither inside the local folder:
wordpress:
...
image: my_project_image
volumes: ['./testdir:/var/www/html']
...
#volumes:
# volumetest:
Is there a way I can create the file just by using the docker-compose?
Thanks a lot. :)
At the build stage of the Docker image, you copy test.html to /var/www/html directory inside the Docker image.
When you run the image as a container, you map local ./testdir to /var/www/html directory inside the container. This means /var/www/html now points to your ./testdir directory. If your ./testdir does not contain your test.html file, then you're not going to see it inside the container as well.
I believe you misunderstood that when you use volumes, it will copy the file in the image to the local file system. What happens is, whichever files you have in your local file system directory will reflect (you can think of this as replace as well) as the files in the mapped directory inside the container.
A few lines from docker-compose.yml
volumes:
- ./yii-application:/app/yii-application
- /app/yii-application/common/config/
First line adds to the volume an entire application.
The second one makes some sort of exclude of config folder, so I do not need my config from host machine.
A few lines from Dockerfile
COPY ./config-${APP_ENV}/common /app/yii-application/common/config
Instead of COPY I tried
RUN cp -a /app/config-${APP_ENV}/common/. /app/yii-application/common/config
It does not work either.
I think there is an issue in the order of the commands that are being executed:
When you are building and image with Dockerfile, you are coping the code inside dir /app/yii-application/common/config.
Then, you are mounting a volume: volumes: /app/yii-application/common/config/
and overwriting the existing dir with an empty dir that serves as a volume.
You need to work around that issue.
I moved the config files out of volume directory.
So, now the volume looks like this
volumes:
- ./yii-application:/app/yii-application
Config files in
./