Overwrite volume contents with container's contents - docker

I have a volume which contains data that needs to stay persisted. When creating the volume for the first time, and mounting it to my node container, all container contents are copied to the volume, and everything behaves as expected. The issue is that when I change a few files in my node container, I remove the old image and container, and rebuild them from scratch. When running the updated container, the container's files don't get copied into the volume. This means that the volume still contains the old files, and therefore when the volume is mounted in the container, no updated functionality is present, and I have to remove and recreate the volume from scratch, which I can't do since the volume's data needs to be persisted.
Here is my dockerfile
FROM mcr.microsoft.com/dotnet/sdk:5.0
COPY CommandLineTool App/CommandLineTool/
COPY NeedBackupNodeServer App/NeedBackupNodeServer/
WORKDIR /App/NeedBackupNodeServer
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash - \
&& apt update \
&& apt install -y nodejs
EXPOSE 5001
ENTRYPOINT ["node", "--trace-warnings", "index.js"]
Here are my commands and expected output
docker volume create nodeServer-and-commandLineTool-volume
docker build -t need-backup-image -f Dockerfile .
docker run -p 5001:5001 --name need-backup-container -v nodeServer-and-commandLineTool-volume:/App need-backup-image -a
when running
docker exec need-backup-container cat index.js
the file is present and contains the latest updates, since the volume was just created.
Now when I update some files, I need to rebuild the image and the container, so I run
docker rm need-backup-container
docker rmi need-backup-image
docker build -t need-backup-image -f Dockerfile .
docker run -p 5001:5001 --name need-backup-container -v nodeServer-and-commandLineTool-volume:/App need-backup-image -a
Now I thought that when running
docker exec need-backup-container cat index.js
I'd see the updated file changes, but nope, I only see the old files that were first created when the volume was mounted for the first time.
So my question is, is there anyway to achieve overwriting the volume's files when creating a container?

If your application needs persistent data, it should be stored in a different directory from the application code. This can be in a dedicated /data directory or in a subdirectory of your application; the important thing is that, when you mount a volume to hold the persistent data, it does not hide your application code.
In a Node application, for example, you could refer to a ./data for your data files:
import { open } from 'fs/promises';
import { join } from 'path';
const dataDir = process.env.DATA_DIR || 'data';
const fh = await open(join(dataDir, 'file.txt'), 'rw');
Then in your Dockerfile you'd need to create that directory. If you set up a non-root user, that directory, but not your code, should be owned by the user.
FROM node:lts
# Create the non-root user
RUN adduser --system --no-create-home nonroot
# Install the Node application normally
WORKDIR /app
COPY package*.json .
RUN npm ci
COPY index.js .
# Create the data directory
RUN mkdir data && chown nonroot data
# Specify how to run the container
USER nonroot
CMD ["node", "index.js"]
Then when you launch the container, mount the volume only on the data directory, not over the entire /app tree.
docker run \
-p 5001:5001 \
--name need-backup-container \
-v nodeServer-and-commandLineTool-volume:/app/data \
need-backup-image
# ^^^^^^^^^
Note that the Dockerfile as shown here would also let you use a host directory instead of a Docker named volume, and specify the host uid when you run the container. You do not need to make any changes to the image to do this.
docker run \
-p 5002:5001 \
--name same-image-with-bind-mount \
-u $(id -u) \
-v "$PWD/app-data:/app/data" \
need-backup-image

Related

docker image - mounted volume is empty

I am building my first docker image. I am a beginner
It is a simple python http server. This is my DockerFile
FROM python:3.8.0-slim
WORKDIR /src
COPY src/ .
CMD [ "python", "-m", "http.server", "--cgi", "8000"]
I have a config folder in /src with some config files.
I named the image "my-server"
I create a container with
docker run -d \
--name "my-server" \
-p 8000:8000 \
-v /dockerdata/appdata/my-server/config/:/src/config \
--restart unless-stopped \
my-server
the issue is /dockerdata/appdata/my-server/config/ is empty on my host.
I see this done on all docker images on dockerhub I use and the mounted volumes are not empty for these images.
How do they do it?
Their startup sequence explicitly copies source files into the volume, or otherwise creates them. A Docker mount always replaces the content in the image with the content of whatever's being mounted; there is no way to mount the container content to the host.
(The one exception to this "always" is, if you're using native Docker, and you're mounting a named volume, and the named volume is empty, then content from the image is copied into the volume first; but the content is never ever updated, it only works for named volumes and not other kinds of mounts, and it doesn't work on other environments like Kubernetes. I would not rely on this approach.)
If the configuration is a single file, this isn't a huge imposition. You probably already need to distribute artifacts like a docker-compose.yml file separately from the image, so distributing a default configuration isn't much more. If defaults are compiled into your application and an empty configuration is valid, this also simplifies things. Another helpful approach could be to have a search path for configuration files, and read both a "user" and "system" configuration.
If you do need to copy files out to a host directory or other mount point, I would generally do this with an entrypoint wrapper script. You will need to keep a copy of the configuration in the image somewhere that's not the actual config directory so that you can copy it when it doesn't exist. The script can be fairly straightforward:
#!/bin/sh
# Copy the default configuration if it doesn't exist
if [ ! -f config/config.yml ]; then
cp default-config/config.yml config
fi
# Run the main container command
exec "$#"
You may need to do some shuffling in your Dockerfile; the important thing is to make this script be the ENTRYPOINT but leave the CMD unchanged.
# Save the "normal" config away; the entrypoint script will create
# the "real" config if one isn't mounted
RUN mv config default-config \
&& mkdir config
# Launch the server via the entrypoint wrapper
ENTRYPOINT ["./entrypoint.sh"] # must be JSON array syntax
CMD ["python", "-m", "http.server", "--cgi", "8000"] # unchanged
This is expected, bind mount to a container directory will result to the content in the directory to be obscured. If you mount to a named volume, the directory’s contents are copied into the volume.
docker run -d \
--name "my-server" \
-p 8000:8000 \
-v myvol:/src/config \
--restart unless-stopped \
my-server
Now if you run docker run -it -v myvol:/config --rm busybox ls /config you will see the copied content.

Copy files from container to local in Docker

I want to copy a file from container to my local. The file is generated after execute python script, but due to then ENTRYPOINT, the container exited right after it run, and cant be able to use docker cp command. Any idea on how to prevent the container from exit before manage to copy the file? Below is my Dockerfile:
FROM python:3.9-alpine3.12
WORKDIR /app
COPY . /app/
RUN pip install --no-cache-dir -r requirements.txt && \
rm -f /var/cache/apk/*
ENTRYPOINT ["python3", "main.py"]
I use this command to run the image:
docker run -d -it --name test [image]
If the output file is stored in it's own directory (say /app/output) you can run: docker run -d -it -v $PWD/output:/app/output/ --name test [image] and the file will be in the output directory of the current directory.
If it's not, then run the container with: docker run -d -it --name test [image]
Then copy the file to your own filesystem using docker cp test:/app/example.json . to copy it to the current directory.
If running a container in background is unnecessary then you can copy a file from stdout
docker run -it [image] cat /app/example.json > out_example.json

Cannot copy files from source folder to Docker image

I'm trying to copy the folder and contents from a source to a Docker container using an image. I built the image from my Dockerfile.
RUN useradd -m -s /bin/bash user1 && \
ln -s /foo /home/user1/foo
RUN mkdir /foo && && chown -R user1:user1 /foo
VOLUME /foo
After I build the Docker image, I run these commands to create a container.
docker run -it
--name container_name \
--mount "type=bind,source=$(pwd)/$FOLDER/container_foo,destination=/foo/" \
dockerimage:tag
The files from the source folder /foo isn't in the /container_foo. I checked by doing docker exec -it container_ID /bin/bash and confirmed that the files aren't there.
EDIT :
I just found out that mount only goes one way, exposing local host files/folder to the docker container. It doesn't copy the files inside of the docker container to the local folder when mounting. I removed creating the /foo directory from RUN and VOLUME. Instead I did this in the Dockerfile.
COPY --chown=user1:user1 foo/ foo/
And I was able to copy the files from source. Now I just need to copy it from there to container_foo when doing the docker run ... command.

Docker not saving a file created using python - Flask application

I created a Flask Application. This application receives a XML from a url and saves it:
response = requests.get(base_url)
with open('currencies.xml', 'wb') as file:
file.write(response.content)
When I run the application without Docker, the file currencies.xml is correctly created inside my app folder.
However, this behaviour does not occur when I use docker.
In docker I run the following commands:
docker build -t my-api-docker:latest .
docker run -p 5000:5000 my-api-docker ~/Desktop/myApiDocker # This is where I want the file to be saved: inside the main Flask folder
When I run the second command, I get:
docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"/Users/name/Desktop/myApiDocker\": stat /Users/name/Desktop/myApiDocker: no such file or directory": unknown.
ERRO[0001] error waiting for container: context canceled
But If I run:
docker build -t my-api-docker:latest .
docker run -p 5000:5000 my-api-docker # Without specifying the PATH
I can access the website (but it is pretty useless without the file currencies.xml
Dockerfile
FROM python:3.7
RUN pip install --upgrade pip
COPY ./requirements.txt /app/requirements.txt
WORKDIR /app
RUN pip install -r requirements.txt
COPY . /app
EXPOSE 5000
CMD [ "flask", "run", "--host=0.0.0.0" ]
When you
docker run -p 5000:5000 my-api-docker ~/Desktop/myApiDocker
Docker interprets everything after the image name (my-api-docker) as the command to run. It runs /Users/name/Desktop/myApiDocker as a command, instead of what you have as the CMD in the Dockerfile, and when that path doesn't exist in the container, you get the error you see.
It's a little unlikely you'll be able to pass this path to your flask run command as a command-line argument. A typical way of dealing with this is by using an environment variable instead. In your code,
download_dir = os.environ.get('DOWNLOAD_DIR', '.')
currencies_xml = os.path.join(download_dir, 'currencies.xml')
with open(currencies_xml, 'wb') as file:
...
Then when you start your container, you can pass that as an environment variable with the docker run -e option. Note that this names a path inside the container; there's no particular need for this to match the path on the host.
docker run \
-p 5000:5000 \
-e DOWNLOAD_DIR=/data \
-v $HOME/Desktop/myApiDocker:/data \
my-api-docker
It's also fairly common to put an ENV statement in your Dockerfile or otherwise pick a fixed path for this, and just specify that your image's interface is that it will download the file into whatever is mounted on /data.
When you docker run the image, the process' context is the container's file system not your host's file system. So my-api-docker ~/Desktop/myApiDocker (attempts to) place the file in the container's (!) ~/Desktop.
Instead you need to mount one of your host's directories into the container's file system and store the file in the mounted directory.
Something like:
docker run ... \
--volume=[HOST-PATH]:[CONTAINER-PATH] \
... \
my-api-docker [CONTAINER-PATH]/thefile
The container then writes the file to [CONTAINER-PATH]/thefile but this is mapped to the host's [HOST-PATH]/thefile.
NB The values for [HOST-PATH] and [CONTAINER-PATH] must be absolute not relative paths.
You may prove this behavior to yourself using e.g. either python:3.7 or busybox:
# List my host's root
ls -l /
# List the container's root
docker run --rm busybox ls -l /
# Mount the host's /tmp into the container's /tmp
ls -l /tmp
docker run --rm --volume=/tmp:/tmp busybox ls -l /tmp
HTH!

run docker container as a arbitrary user passed to it while running the image

I want to run a docker container as an arbitrary user which is passed to the image while running it. For example docker run -u 1000 myimage.
The above is possible. However I want to create a home directory with this user 1000 while starting the container(possibly through CMD) and do my container service stuff within that directory.
Is this possible and some pointers would be useful on ways to achieve it.
First save your current user and group in variables:
export uid=$(id -u)
export gid=$(id -g)
Then to run your image,you have two options:
1) Run the image from the location of the app directory itself:
sudo docker run -d \
--user $uid:$gid \
-v $(pwd):/home/$USER \
--workdir="/home/$USER" \
myimage
2) Create a new directory for the app, e.g. at /home/$USER/app, but then you will have to write in command line your CMD from the docker file.
For example if this was your Dockerfile:
FROM node:7
WORKDIR /app
COPY package.json /app
COPY . /app
CMD node bin/www
Your would run it like that:
sudo docker run -d \
--user $uid:$gid \
-v $(pwd):/home/$USER \
--workdir="/home/$USER" \
hello-express \
bash -c "cp -rf /app/* /home/$USER/; node bin/www"
Here you pass the user to the container using $uid:$gid and you mount the user's home directory as a volume and then set it as the working directory.
I know it's quite complex, but it's the only way to achieve exactly what you want.
If you want a simpler solution, consider planning it differently. See this example for running a docker container as a non-root user.

Resources