Docker in Docker cannot mount volume - linux

I am running a Jenkins cluster where in the Master and Slave, both are running as a Docker containers.
The Host is latest boot2docker VM running on MacOS.
To allow Jenkins to be able to perform deployment using Docker, I have mounted the docker.sock and docker client from the host to the Jenkins container like this :-
docker run -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):/usr/bin/docker -v $HOST_JENKINS_DATA_DIRECTORY/jenkins_data:/var/jenkins_home -v $HOST_SSH_KEYS_DIRECTORY/.ssh/:/var/jenkins_home/.ssh/ -p 8080:8080 jenkins
I am facing issues while mounting a volume to Docker containers that are run inside the Jenkins container. For example, if I need to run another Container inside the Jenkins container, I do the following :-
sudo docker run -v $JENKINS_CONTAINER/deploy.json:/root/deploy.json $CONTAINER_REPO/$CONTAINER_IMAGE
The above runs the container, but the file "deploy.json" is NOT mounted as a file, but instead as a "Directory". Even if I mount a Directory as a Volume, I am unable to view the files in the resulting container.
Is this a problem, because of file permissions due to Docker in Docker case?

A Docker container in a Docker container uses the parent HOST's Docker daemon and hence, any volumes that are mounted in the "docker-in-docker" case is still referenced from the HOST, and not from the Container.
Therefore, the actual path mounted from the Jenkins container "does not exist" in the HOST. Due to this, a new directory is created in the "docker-in-docker" container that is empty. Same thing applies when a directory is mounted to a new Docker container inside a Container.
Very basic and obvious thing which I missed, but realized as soon I typed the question.

Lots of good info in these posts but I find none of them are very clear about which container they are referring to. So let's label the 3 environments:
host: H
docker container running on H: D
docker container running in D: D2
We all know how to mount a folder from H into D: start D with
docker run ... -v <path-on-H>:<path-on-D> -v /var/run/docker.sock:/var/run/docker.sock ...
The challenge is: you want path-on-H to be available in D2 as path-on-D2.
But we all got bitten when trying to mount the same path-on-H into D2, because we started D2 with
docker run ... -v <path-on-D>:<path-on-D2> ...
When you share the docker socket on H with D, then running docker commands in D is essentially running them on H. Indeed if you start D2 like this, all works (quite unexpectedly at first, but makes sense when you think about it):
docker run ... -v <path-on-H>:<path-on-D2> ...
The next tricky bit is that for many of us, path-on-H will change depending on who runs it. There are many ways to pass data into D so it knows what to use for path-on-H, but probably the easiest is an environment variable. To make the purpose of such var clearer, I start its name with DIND_. Then from H start D like this:
docker run ... -v <path-on-H>:<path-on-D> --env DIND_USER_HOME=$HOME \
--env DIND_SOMETHING=blabla -v /var/run/docker.sock:/var/run/docker.sock ...
and from D start D2 like this:
docker run ... -v $DIND_USER_HOME:<path-on-D2> ...

Another way to go about this is to use either named volumes or data volume containers. This way, the container inside doesn't have to know anything about the host and both Jenkins container and the build container reference the data volume the same way.
I have tried doing something similar to what you are doing, except with an agent rather that using the Jenkins master. The problem was the same in that I couldn't mount the Jenkins workspace in the inner container. What worked for me was using the data volume container approach and the workspace files were visible to both the agent container and the inner container. What I liked about the approach is the both containers reference the data volume in the same way. Mounting directories with an inner container would be tricky as the inner container now needs to know something about the host that its parent container is running on.
I have detailed blog post about my approach here:
http://damnhandy.com/2016/03/06/creating-containerized-build-environments-with-the-jenkins-pipeline-plugin-and-docker-well-almost/
As well as code here:
https://github.com/damnhandy/jenkins-pipeline-docker
In my specific case, not everything is working the way I'd like it to in terms of the Jenkins Pipeline plugin. But it does address the issue of the inner container being able to access the Jenkins workspace directory.

Regarding your use case related to Jenkins, you can simply fake the path by creating a symlink on the host:
ln -s $HOST_JENKINS_DATA_DIRECTORY/jenkins_data /var/jenkins_home

If you are like me and don't want to mess with Jenkins Setup or too lazy to go through all this trouble, here is a simple workaround I did to get this working for me.
Step 1 - Add following variables to the environment section of pipeline
environment {
ABSOLUTE_WORKSPACE = "/home/ubuntu/volumes/jenkins-data/workspace"
JOB_WORKSPACE = "\${PWD##*/}"
}
Step 2 - Run you container with following command Jenkins pipeline as follows.
steps {
sh "docker run -v ${ABSOLUTE_WORKSPACE}/${JOB_WORKSPACE}/my/dir/to/mount:/targetPath imageName:tag"
}
Take note of the double quotes in the above statement, Jenkins will not convert the env variables if the quotes are not formatted properly or single quotes are added instead.
What does each variable signify?
ABSOLUTE_WORKSPACE is the path of our Jenkins volume which we had mounted while starting Jenkins Docker Container. In my case, the docker run command was as follows.
sudo docker run \
-p 80:8080 \
-v /home/ubuntu/volumes/jenkins-data:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-d -t jenkinsci/blueocean
Thus the varible ABSOLUTE_WORKSPACE=/home/ubuntu/volumes/jenkins-data + /workspace
JOB_WORKSPACE command gives us the current workspace directory where your code's lives. This is also the root dir of your code base. Just followed this answer for reference.
How does this work ?
It is very straight forward, as mentioned in #ZephyrPLUSPLUS ( credits where due ) answer, the source path for our docker container which is being run in Jenkins pipeline is not the path in current container, rather the path taken is host's path. All we are doing here is constructing the path where our Jenkins pipeline is being run. And mounting it to our container. Voila!!
Here's a little illustration to help clarify ...

This also works via docker-compose and/or named volumes so you don't need to create a data only container, but you still need to have the empty directory on the host.
Host setup
Make host side directories and set permissions to allow Docker containers to access
sudo mkdir -p /var/jenkins_home/{workspace,builds,jobs} && sudo chown -R 1000 /var/jenkins_home && sudo chmod -R a+rwx /var/jenkins_home
docker-compose.yml
version: '3.1'
services:
jenkins:
build: .
image: jenkins
ports:
- 8080:8080
- 50000:50000
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- workspace:/var/jenkins_home/workspace/
# Can also do builds/jobs/etc here and below
jenkins-lts:
build:
context: .
args:
versiontag: lts
image: jenkins:lts
ports:
- 8081:8080
- 50001:50000
volumes:
workspace:
driver: local
driver_opts:
type: none
o: bind
device: /var/jenkins_home/workspace/
When you docker-compose up --build jenkins (you may want to incorporate this into a ready to run example like https://github.com/thbkrkr/jks where the .groovy scripts pre-configure Jenkins to be useful on startup) and then you will be able to have your jobs clone into the $JENKINS_HOME/workspace directory and shouldn't get errors about missing files/etc because the host and container paths will match, and then running further containers from within the Docker-in-Docker should work as well.
Dockerfile (for Jenkins with Docker in Docker)
ARG versiontag=latest
FROM jenkins/jenkins:${versiontag}
ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false"
COPY jenkins_config/config.xml /usr/share/jenkins/ref/config.xml.override
COPY plugins.txt /usr/share/jenkins/ref/plugins.txt
RUN /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt
USER root
RUN curl -L http://get.docker.io | bash && \
usermod -aG docker jenkins
# Since the above takes a while make any other root changes below this line
# eg `RUN apt update && apt install -y curl`
# drop back to the regular jenkins user - good practice
USER jenkins
EXPOSE 8080

A way to work around this issue is to mount a directory (inside your docker container in which you mounted your docker socket) using the exact same path for its destination. Then, when you run a container from within that container, you are able to mount anything within that mount's path into the new container using docker -v.
Take this example:
# Spin up your container from which you will use docker
docker run -v /some/dir:/some/dir -v /var/run/docker.sock:/var/run.docker.sock docker:latest
# Now spin up a container from within this container
docker run -v /some/dir:/usr/src/app $CONTAINER_IMAGE
The folder /some/dir is now mounted across your host, the intermediate container as well as your destination container. Since the mount's path exists on both the host as the "nearly docker-in-docker" container, you can use docker -v as expected.
It's kind of similar to the suggestion of creating a symlink on the host but I found this (at least in my case), a cleaner solution. Just don't forget to cleanup the dir on the host afterwards! ;)

I have same problem in Gitlab CI, I solved this by using docker cp to do something like mount
script:
- docker run --name ${CONTAINER_NAME} ${API_TEST_IMAGE_NAME}
after_script:
- docker cp ${CONTAINER_NAME}:/code/newman ./
- docker rm ${CONTAINER_NAME}

Based from the description mentioned by #ZephyrPLUSPLUS
here is how I managed to solve this:
vagrant#vagrant:~$ hostname
vagrant
vagrant#vagrant:~$ ls -l /home/vagrant/dir-new/
total 4
-rw-rw-r-- 1 vagrant vagrant 10 Jun 19 11:24 file-new
vagrant#vagrant:~$ cat /home/vagrant/dir-new/file-new
something
vagrant#vagrant:~$ docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock docker /bin/sh
/ # hostname
3947b1f93e61
/ # ls -l /home/vagrant/dir-new/
ls: /home/vagrant/dir-new/: No such file or directory
/ # docker run -it --rm -v /home/vagrant/dir-new:/magic ubuntu /bin/bash
root#3644bfdac636:/# ls -l /magic
total 4
-rw-rw-r-- 1 1000 1000 10 Jun 19 11:24 file-new
root#3644bfdac636:/# cat /magic/file-new
something
root#3644bfdac636:/# exit
/ # hostname
3947b1f93e61
/ # vagrant#vagrant:~$ hostname
vagrant
vagrant#vagrant:~$
So docker is installed on a Vagrant machine. Lets call it vagrant. The directory you want to mount is in /home/vagrant/dir-new in vagrant.
It starts a container, with host 3947b1f93e61. Notice that /home/vagrant/dir-new/ is not mounted for 3947b1f93e61.
Next we use the exact location from vagrant, which is /home/vagrant/dir-new as the source of the mount and specify any mount target we want, in this case it is /magic. Also note that /home/vagrant/dir-new does not exist in 3947b1f93e61.
This starts another container, 3644bfdac636.
Now the contents from /home/vagrant/dir-new in vagrant can be accessed from 3644bfdac636.
I think because docker-in-docker is not a child, but a sibling. and the path you specify must be the parent path and not the sibling's path. So any mount would still refer to the path from vagrant, no matter how deep you do docker-in-docker.

You can solve this passing in an environment variable.
Example:
.
├── docker-compose.yml
└── my-volume-dir
└── test.txt
In docker-compose.yml
version: "3.3"
services:
test:
image: "ubuntu:20.04"
volumes:
- ${REPO_ROOT-.}/my-volume-dir:/my-volume
entrypoint: ls /my-volume
To test run
docker run -e REPO_ROOT=${PWD} \
-v /var/run/docker.sock:/var/run/docker.sock \
-v ${PWD}:/my-repo \
-w /my-repo \
docker/compose \
docker-compose up test
You should see in the output:
test_1 | test.txt

Related

How to open remote shell to node.js container under docker-compose (Alpine linux)

I have a docker-compose.yml configuration file with several containers and one of the containers is node.js docker instance.
By some reason the docker instance returns error during start. In the result it's not possible to connect to the node.js container and investigate issue.
What is the simplest way to connect to the broken node.js under Alpine linux?
Usually in my docker-compose.yml
I just replace the command or entrypoint by :
command: watch ps
It's a bit hackish, but that keeps the container up.
Alternatively, once the image has been built, you can run it using docker. But then you have to do what you did in your docker-compose.yml file in your command, like mount volumes and open ports manually.
FOR DOCKER-COMPOSE
In case if you use docker-compose the simplest way is to add the following command line into your docker-compose.yml file.
services:
api:
build: api/.
command: ["/bin/sh", "-c", "while sleep 3600; do :; done"]
depends_on:
- db
- redis
...
also it need to comment line by line from bottom up inside the Dockerfile for node.js until the container will be able to start.
After the node.js container will be able to start you can easy connect to your container via
docker exec -it [container] sh
FOR DOCKER
You can simply add at the end of Dockerfile the following line
CMD echo "^D for exit" && wc -
and comment line by line (from bottom up) above this line until the container will be able to start.
You can docker-compose run an alternate command. This requires no changes in your Dockerfile or docker-compose.yml. For example,
docker-compose run --rm web /bin/sh
This creates a new container which is configured identically to what is requested in the docker-compose.yml (with environment variables and mounted volumes), except that ports: aren't published by default. It is essentially identical to docker run with the same options, except it defaults to -i -t being on.
If your Dockerfile uses ENTRYPOINT instead of CMD to declare the main container command, you need the same --entrypoint option. For example, to get a listing of the files in the image's default working directory, you could
docker-compose run --rm --entrypoint /bin/ls web -l
(If your ENTRYPOINT is a wrapper script that ultimately exec "$#" you don't need this.)

Can I run docker diff from a container on the same host as the container I want to run the diff on?

I have two containers running on a host. When I'm in container A I want to run a diff on container B compared to it's image to see what has changed in the filesystem. I know this can be ran easily from the host itself, but I'm wondering is there any way of doing this from inside container A, to see the difference on container B?
You can run any docker commands from within container which will communicate with host docker daemon if:
You have access to docker socket inside container
You have docker client inside container
You can achieve first condition by mounting docker socket to container - add following to your docker run call:
-v /var/run/docker.sock:/var/run/docker.sock.
The second condition depends on your docker image.
If you are running bare Ubuntu image you can have shell inside container which will be able to do what you want with following command:
docker run -it -v /var/run/docker.sock:/var/run/docker.sock ubuntu:latest sh -c "apt-get update ; apt-get install docker.io -y ; bash"

Mouting relative folders in docker-compose when using docker daemon via a container?

We have previously been running Jenkins in Docker in Docker (DIND) mode, i.e. running a docker daemon inside the Jenkins docker container. But due to many problems (some of which are described in the link above) we've decided to move away from this approach and instead let the container use the host daemon by simply mounting it as volume when starting the container:
-v /var/run/docker.sock:/var/run/docker.sock
But now we run into problems when mounting relative paths with Docker Compose that is started inside the container which worked fine in DIND mode. Consider this docker-compose file:
myimage:
build: .
environment:
LANG: C.UTF-8
working_dir: /code
volumes:
- ../../../:/code
- ~/.m2/repository:/root/.m2/repository
- ~/.gradle:/root/.gradle
Previously this mounted all folders, for example the ../../../ folder, from the container but now it seems to try to mount them from the host. When I check the directory structure on the host it seems like docker-compose have replicated the directory structure from the container and then tries to mount this folder which makes it empty.
So my question is, how can one mount relative paths in Docker Compose when using the docker daemon from the host?
You'll need to make sure the relative path on your host is the same inside your Jenkins container for this to work.
It's not really a relative path, docker-compose is doing the best it can to convert a relative path into an absolute path which the docker host requires. All paths are evaluated on the docker host to create a new container, it doesn't know you are running the docker client remotely or inside of a container, and it doesn't know what directory you are currently in.
As another option, you may want to consider switching to named volumes and map the same named volume in your Jenkins container as in your other containers.
Docker has a client server architecture, when you mount the docker socket which is on the host, you are simply communication with the hosts docker deamonm. Thus all host volume path will be interpreted as path on the host.
To solve that you need to bind the jenkins containers directories onto the host and then use the host folders as mount points. Thus simply start jenkins container with
-v ./code:<path-to-../../..> -v ./m2-repo:.../.m2/repository
Then change the compose file to use the host folders:
myimage:
build: .
environment:
LANG: C.UTF-8
working_dir: /code
volumes:
- ./code:/code
- ./m2-repo:/root/.m2/repository
...

How to launch a Docker container that i've got from another person?

I am a Docker-newbie and I've got a project from another developer including a Dockerfile. This shall give me the Virtual Machine to continue work with the (nodeJS-) project inside this project folder.
Docker is already installed on my machine.
How can I launch this container now?
I've read about a command
sudo docker run -name my_first_instance
but i can't find any container name in the Dockerfile.
The dockerfile will create an image for you that you can launch containers from. this being said , Follow this:
Create a folder.
Copy dockerfile in the folder
cd into the folder execute the following command:
docker build -t <your desired image name> .
This will create an image using directives in the dockerfile in the current folder.
Now launch a container from the image.
docker run -d --name <your container name> <imagename from previous step> <optional startup commands>
Useful docker commands:
You can expose ports in the previous command using -p switch.
You can list Images via docker images
You can list running containers via docker ps
you can list running + exited containers via docker ps -a
have a look at
https://hub.docker.com/_/node/
it is the official repository for NodeJS docker images
If you want some docker images based on NodeJS, you will need to pull them docker pull my_node_image
Then you can launch one with such a command
docker run -it --rm --name my-running-script -v "$PWD":/usr/src/app -w /usr/src/app node:4 node your-daemon-or-script.js
The Dockerfile is just the recipe to build a docker image, for Nginx, Mysql, MongoDb, Redis, Wordpress, Spotify, atop, htop...
If docker images shows nothing, it means you have not yet pulled any docker image.

Docker is not adding a network shared folder as a data volume

I have a Ubuntu host with a mounted network folder located at /mnt/mynetworkshare.
When I run my docker image, I want to expose the /mnt/mynetworkshare folder on my Ubuntu host so I run the container with the command:
docker run -it --rm -v /mnt/mynetworkshare/:/opt/shared_folder/ brspurri/my-app:latest /bin/bash
However, when I'm inside the bash shell of the container, the folder /opt/shared_folder/ exists, but no files are present or visible.
When I change the host folder to a local one using:
docker run -it --rm -v /mnt/mylocalfolder/:/opt/shared_folder/ brspurri/my-app:latest /bin/bash
all the contents of /mnt/mylocalfolder/ are visible and accessible in /opt/shared_folder/
Does anyone have any idea why this could be happening? Is it possible to share a network folder with a Docker container?

Resources