How would I add an nginx reverse proxy to an existing image?
For example I use https://github.com/Koenkk/zigbee2mqtt and this exposes a front end that only works over http.
I want to use the image but have the front end accessible only over https. Adding a Nginx reverse proxy is quite simple but what is the best method?
Do I fork the repository and add Nginx and config into the build or do I create a new image based on the existing one?
Presumably if I create a new image based on the existing one the entrypoint is essentially lost so any setup done in that script would need to be replicated in my image?
Edit: I need to run this container on a macvlan network but I do not want the insecure http access available to other devices on that network.
In this particular image there was an entrypoint and the best way I have found of doing this to override the entrypoint when running it.
The original entrypoint was /usr/local/bin/docker-entrypoint.sh and there was a CMD of npm start
After some hacking around (As I still cannot quite get my head around the combination of entry point and CMD) I created the following script which installs nginx if it insn't already installed as well as running the original entrypoint and CMD
The script and the config is on a volume so this persists across different instances of the image.
#!/bin/sh
echo "Hello"
. /usr/local/bin/docker-entrypoint.sh
if [[ `apk -e info nginx | wc -l ` -eq 0 ]]
then
apk add nginx
cd /etc/nginx/conf.d
mv default.conf default.conf.disabled
ln -s /app/data/nginx.conf nginx.conf
mkdir /run/nginx
echo "Installed Nginx"
fi
echo "Starting Nginx"
nginx &
npm init -y
echo "Starting Node"
npm start
Not sure if this is advisable or the best way of doing this but it seems to work reasonably well.
Related
I have the below Dockerfile to create a nodejs server. It clones a repo that contains the script that starts the node servers but that script needs to be executable.
The first time I start this docker container, it works as expected. I know the chown is doing something because without it, the script "does not exist" because it does not have the x permissions.
However, if I then open a TTY into the container and inspect the file, the permissions have not changed. If the container is restarted, it gives an error that the file does not exist (x permission is missing). If I execute the chmod manually after the first run, I can restart the container without issues.
I have tried it as part of the combined RUN command (as below) and as a separate RUN command, both written normally and as RUN ["chmod", "755" "/opt/youtube4me/start_all.sh"] and both with 755 and +x. All of those ways work the first start but do not permanently change the permissions.
I'm very new to docker and I think I'm missing some basic concept that explains that chmod is run in some sort of context (maybe?)
FROM node:10.19.0-alpine
RUN apk update && \
apk add git && \
git clone https://github.com/0502-crew/youtube4me.git /opt/youtube4me && \
chmod 755 /opt/youtube4me/start_all.sh
COPY config/. /opt/youtube4me/
EXPOSE 45011
WORKDIR /opt/youtube4me
CMD ["/opt/youtube4me/start_all.sh"]
UPDATE 1
I run these commands with docker:
sudo docker build . -t 0502-crew/youtube4me
sudo docker container run -d --name youtube4me --network host 0502-crew/youtube4me
start_all.sh
#!/bin/sh
# Reset to master in case any force pushes were done
git reset --hard origin/master
# Start client server
cd client && npm i && npm run build && nohup npm run prod &
# Start api server
cd api && npm i && npm run build && nohup npm run prod &
# Keep server running by tailing nothing, forever...
tail -f /dev/null
The config folder from which I COPY just contains 2 .ts files that are in .gitignore so need to be added manually:
config/api/src/config/Config.ts
config/client/src/config/Config.ts
UPDATE 2
If I replace the CMD line with CMD ["sh", "-c", "tail -f /dev/null"] then the x permissions remain on the sh file. It seems that executing the script removes the permissions...
As #dennisvandehoef pointed out, the fundamentals of my approach are off. Including a git clone in the Dockerfile will, for example, have as side effect that building the image a second time will not clone the repo again, even if files have changed.
However, I did find a solution to this approach so I might as well share it for the sake of knowledge sharing:
I develop on Windows so I don't need to set permissions to a script but Linux does need it. I found out you can still set the +x permission bit using git on Windows and commit it. When docker clones the repo, chmod is then no longer needed at all.
git update-index --chmod=+x start_all.sh
If you then ls the files with this command
git ls-files --stage
you will see that all other files are 100644 but the sh file is 100755 (so permission 755).
Next just commit it
git commit -m"Made sh executable"
I had to delete my docker images afterwards and rebuild it to ensure the git clone was performed again.
I'll rewrite my dockerfile at a later point but for now it works as intented.
It is still a mystery to me that the x bit disappears when the chmod is part of the Dockerfile
You are building a docker-file for the project you are pulling from git and your start_all script includes a git reset --hard origin/master which is a bad practice since now you cannot create versions of your docker images.
Also you copy these 2 files to the wrong directory. with COPY config/. /opt/youtube4me/ you copy them directly to the rood of your project. and not to the given locations in config/api/src/config/ and config/client/src/config/Config.ts
I realize that fixing these problems will not fix this chmod problem itself. But it might make it go away for your specific use case.
Also if it are secrets you are excluding from git, you also should not add them to your docker-image while building since then (after you pushed it to docker) it will be public again. Therefore it is a good practice to never add them while building.
Did you try something like this:
Docker file
FROM node:10.19.0-alpine
RUN mkdir /opt/youtube4me
COPY . /opt/youtube4me/
WORKDIR /opt/youtube4me
RUN chmod 755 /opt/youtube4me/start_all.sh
EXPOSE 45011
CMD ["/opt/youtube4me/start_all.sh"]
dockerignore
api/src/config/Config.ts
client/src/config/Config.ts
script
# Start client server
cd client && npm i && npm run build && nohup npm run prod &
# Start api server
cd api && npm i && npm run build && nohup npm run prod &
# Keep server running by tailing nothing, forever...
tail -f /dev/null
You then need to run docker run with 2 volumes to add the config to it. This is been done with -v. example: docker run -v $(pwd)config/api/src/config/:api/src/config/ -v $(pwd)config/client/src/config/:client/src/config/
Never the less I wonder why you are running both services in one docker image. If this is just for local execution you can also think about creating 2 separate docker images, and use docker-compose to spawn it all.
Addition 1:
I thought a bit about this and it is also a good practice to use environment variables to config a docker-image instead of adding a file to it. You might want to switch to that as well. I advise reading this article to get a better understanding of the why and other possibilities to do this.
Addition 2:
I created a pull request on your code, that is an example of how it could look with docker-compose. It currently does not build because of the config options. But it will give you some more insights.
I have a non-critical Docker Compose project where the Traefik rules vary acceptably between dev and production (I need Lets Encrypt on prod, but not on dev). I am using the [file] config provider.
Currently I am creating separate builds for dev and prod, thus:
# This is fetched from the Compose config
ARG BUILD_NAME
RUN if [ "$BUILD_NAME" == "prod" ]; then \
echo Compiling prod config... ; \
sh /root/compile/prod.sh > /etc/traefik/traefik.toml ; \
else \
echo Compiling dev config... ; \
sh /root/compile/dev.sh > /etc/traefik/traefik.toml ; \
fi
While this project is not enormously important, per-env builds is a bit hacky, and I'd rather go with the standard container approach of one image for all environments.
To do that, I was thinking of doing something like this:
FROM traefik:1.7
# This is set in the Docker Compose config
ENV ENV_NAME
# Let's have a sig handler
RUN wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64
RUN chmod +x /usr/local/bin/dumb-init
COPY docker/traefik/start.sh /root/start.sh
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD ["/root/start.sh"]
The start.sh would have something that would run my "compile" shell command at run time (this selects pieces of config based on the environment). However, the official Traefik images do not run a shell - there are a compiled blob from Go source - so this won't work. Is there a env var by which /etc/traefik/traefik.toml can be changed, or an industry standard way of doing this in Docker?
I did think of using volumes, but that means the container won't "plug-n-play" without additional set up - I like that it is currently self-contained. However, I may use that if there is no alternative. I could run the config "compiler" on the host.
Another approach is to install Traefik in an image that has a shell - maybe it would work with Alpine. I am not sure how I feel about that - removing the shell is a good security feature, so I am hesitant to add it back in, even if I don't think it can be easily exploited.
I didn't find a way to modify the Traefik config file path using environment variables. However, I hit on a volume-based solution that seems to be quite self-contained.
I set up another image called shell in my Docker Compose file:
shell:
build:
context: docker/shell
volumes:
# On-host volume for generating config
- "./docker/traefik/compiled:/root/compiled-host"
This features a bind-mount volume to catch generated config files.
Next, I created a Dockerfile for the new service:
FROM alpine:3.10
COPY compile /root/compile
COPY config /root/config
# Compile different versions of the config, ready to copy into an on-host volume
RUN mkdir /root/compiled && \
sh /root/compile/dev.sh > /root/compiled/traefik-dev.toml && \
sh /root/compile/prod.sh > /root/compiled/traefik-prod.toml
This will create config files for both environments as part of the built image.
When Docker Compose is started up, this service will briefly start, but it will soon exit gracefully and harmlessly. It is intended to be run on an ad-hoc basis anyway.
I already had environment-specific YAML config files, docker-compose-dev.yml and docker-compose-prod.yml, which are explicitly specified in the Compose command with -f. I then used this file to expose the generated on-host file to Traefik. Here's the dev:
traefik:
volumes:
# On-host volume for config
- "./docker/traefik/compiled/traefik-dev.toml:/etc/traefik/traefik.toml"
Much the same was done for traefik-prod.toml
Then, I created per-env commands to copy the config from the shell image into the on-host volume:
#!/bin/sh
docker-compose -f docker-compose.yml -f docker-compose-prod.yml run shell cp /root/compiled/traefik-prod.toml /root/compiled-host/traefik-prod.toml
Finally, when Traefik starts as part of the Compose application, it will find its configuration file in its usual place, /etc/traefik/traefik.toml, but this is in fact a file volume to the generated copy on the host.
I developed a few ROS packages and I want to put the packages in a docker container because installing all the ROS packages all the time is tedious. Therefore I created a dockerfile that uses a base ROS image, installed all the necessary dependencies, copied my workspace, built the workspace in the docker container and sourced everything afterward. You can find the docker file here:
FROM ros:kinetic-ros-base
RUN apt-get update && apt-get install locales
RUN locale-gen en_US.UTF-8
ENV LANG en_US.UTF-8
RUN apt-get update && apt-get install -y \
&& rm -rf /var/likb/apt/lists/*
COPY . /catkin_ws/src/
WORKDIR /catkin_ws
RUN /bin/bash -c '. /opt/ros/kinetic/setup.bash; catkin_make'
RUN /bin/bash -c '. /opt/ros/kinetic/setup.bash; source devel/setup.bash'
CMD ["roslaunch", "master_launch sim_perception.launch"]
The problem is: When I run the docker container wit the "run" command, docker doesn't seem to know that I sourced my new ROS workspace and therefore it cannot launch automatically my launch script. If I run the docker container as bash script with "run -it bash" I can source my workspace again and then roslaunch my .launch file.
So can someone tell me how to write my dockerfile correctly so I launch my .launch file automatically when I run the container? Thanks!
From Docker Docs
Each RUN instruction is run independently and won't effect next instruction so when you run last Line no PATH are saved from ROS.
You need Source .bashrc or every environment you need using source first.
You can wrap everything you want (source command and roslaunch command) inside a sh file then just run that file at the end
If you review the convention of ros_entrypoint.sh you can see how best to source the workspace you would like in the docker. We're all so busy learning how to make docker and ros do the real things, it's easy to skip over some of the nuance of this interplay. This sucked forever for me; hope this is helpful for you.
I looked forever and found what seemed like only bad advice, and in the absence of an explicit standard or clear guidance I've settled into what seems like a sane approach that also allows you to control what launches at runtime with environment variables. I now consider this as the right solution for my needs.
In the Dockerfile for the image you want to set the start/launch behavior;
towards the end; you should use ADD line to insert your own ros_entrypoint.sh (example included); Set it as the ENTRYPOINT and then a CMD to run by default run something when the docker start.
note: you'll (obviously?) need to run the docker build process for these changes to be effective
Dockerfile looks like this:
all your other dockerfile ^^
.....
# towards the end
COPY ./ros_entrypoint.sh /
ENTRYPOINT ["/ros_entrypoint.sh"]
CMD ["bash"]
Example ros_entryppoint.sh:
#!/bin/bash
set -e
# setup ros environment
if [ -z "${SETUP}" ]; then
# basic ros environment
source "/opt/ros/$ROS_DISTRO/setup.bash"
else
#from environment variable; should be a absolute path to the appropriate workspaces's setup.bash
source $SETUP
fi
exec "$#"
Used in this way the docker will automatically source either the basic ros bits... or if you provide another workspace's setup.bash path in the $SETUP environment variable, it will be used in the container.
So a few ways to work with this:
From the command line prior to running docker
export SETUP=/absolute/path/to/the/setup.bash
docker run -it your-docker-image
From the command line (inline)
docker run --env SETUP=/absolute/path/to/the/setup.bash your-docker-image
From docker-compose
service-name:
network_mode: host
environment:
- SETUP=/absolute/path/to/the_workspace/devel/setup.bash #or whatever
command: roslaunch package_name launchfile_that_needed_to_be_sourced.launch
#command: /bin/bash # wake up and do something else
I have a Dockerfile like:
FROM frolvlad/alpine-oraclejdk8:slim
ADD build/libs/zuul*.jar /app.jar
ADD src/main/script/startup.sh /startup.sh
EXPOSE 8080 8999
ENTRYPOINT ["/startup.sh"]
startup.sh looks like:
#!/bin/sh
set -e
if [ $# -eq 0 ]
then
echo "Environment value required"
exit 1
fi
java -jar -Xms400m -Xmx400m -Dlog4j.configurationFile=log4j2-qa2.xml -Djava.security.egd=file:/dev/./urandom -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.rmi.port=8999 app.jar
But when I run it with docker run, I got docker: Error response from daemon: Container command '/startup.sh' not found or does not exist... The shell script has execute permission.
I used the same way to run my other apps and they're all working fine. I wrote the files in Mac and tried to run the container in a Linux machine.
It turns out to be the ^M DOS-style line ending character that caused the issue. But since I'm editing in Mac and I checked several times with vim, I'm pretty sure the starting script in my local machine doesn't have that char. But when opened with vim inside the container, I can see ^M everywhere. So somehow that char gets added to startup.sh when copied into the image, which is weird. That prevents the script from being invoked.
The solution is to add dos2unix filename before the ENTRYPOINT in Dockerfile. But make sure that your base image has that utility.
The shell script has execute permission.
Are you sure though? (I mean within the container, onced ADDed)
To be sure, I would use the Dockerfile:
EXPOSE 8080 8999
COPY src/main/script/startup.sh /startup.sh
RUN chmod 755 /startup.sh
WORKDIR /
ENTRYPOINT ["/startup.sh"]
A container exits when its main process exits. So check that /startup.sh is not ending.
Particularly check that this java
java -jar -Xms400m -Xmx400m -Dlog4j.configurationFile=log4j2-qa2.xml \
-Djava.security.egd=file:/dev/./urandom \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=8999 \
-Dcom.sun.management.jmxremote.local.only=false \
-Dcom.sun.management.jmxremote.rmi.port=8999 \
app.jar
I think the trouble here is when you specify path like /app.jar, it is really difficult to make out where the current working directory actually is.
It can be any where and I suspect Docker must have accidentally copy your start.sh to a place whatever at that point ./ is.
You might want to ssh into the container and do a find to search and see where startup.sh is sitting.
docker ps -a - Find docker container ID
docker -i -t [CONTAINER_ID] /bin/bash - SSH inside
find . -name "startup.sh" - Look for file.
If you intend to copy the files through using ./ (current working directory), I think it would be better to specify where current is. And you can do this using the WORKDIR keyword.
Example:
WORKDIR /path/to/workdir
The WORKDIR instruction sets the working directory for any RUN, CMD,
ENTRYPOINT, COPY and ADD instructions that follow it in the
Dockerfile. If the WORKDIR doesn’t exist, it will be created even if
it’s not used in any subsequent Dockerfile instruction.
That way docker will not get confused.
See:
https://docs.docker.com/engine/reference/builder/#/workdir
I had a similar problem which led me to this thread, however my issue was not quite the exact same as yours. For me the problem was that I was using an alpine base image and my script was referencing #!/bin/bash, I just had to update mine to #!/bin/sh at the top of my script instead.
I have been successful till completely dockerizing my webserver application. Now I want to explore more by deploying them directly to a mesos slave through marathon framework.
I can deploy a docker container in to a marathon in two different approaches , either command line or through marathon web UI.
Both worked for me but challenge is when I am trying to deploy my docker image, marathon frequently restarting a job and in mesos UI page I can see many finished job for the same container. Close to 10 tasks per minute. Which is not expected I believe.
My docker file looks like below:
FROM ubuntu:latest
#---------- file Author / Maintainer
MAINTAINER "abc"
#---------- update the repository sources list
RUN apt-get update && apt-get install -y \
apache2 \
curl \
openssl \
php5 \
php5-mcrypt \
unzip
#--------- installing composer
RUN curl -sS https://getcomposer.org/installer | php
RUN mv composer.phar /usr/local/bin/composer
RUN a2enmod rewrite
#--------- modifying the 000default file
COPY ./ /var/www/airavata-php-gateway
WORKDIR /etc/apache2/sites-available/
RUN sed -i 's/<\/VirtualHost>/<Directory "\/var\/www"> \n AllowOverride All \n <\/Directory> \n <\/VirtualHost>/g' 000-default.conf
RUN sed -i 's/DocumentRoot \/var\/www\/html/DocumentRoot \/var\/www/g' 000-default.conf
WORKDIR /etc/php5/mods-available/
RUN sed -i 's/extension=mcrypt.so/extension=\/usr\/lib\/php5\/20121212\/mcrypt.so/g' mcrypt.ini
WORKDIR /var/www/airavata-php-gateway/
RUN php5enmod mcrypt
#--------- making storage folder writable
RUN chmod -R 777 /var/www/airavata-php-gateway/app/storage
#-------- starting command
CMD ["sh", "-c", "sh pga-setup.sh ; service apache2 restart ; /bin/bash"]
#--------- exposing apache to default port
EXPOSE 80
Now I am clueless how to resolve this issue,any guidance will be highly appreciated.
Thanks
Marathon is meant to run long-running tasks. So in your case, if you start a Docker container that does not keep listening on a specific port, meaning it exits successfully or unsuccessfully, Marathon will start it again.
For example, I started a Docker container using the simplest image hello-world. That generated more than 10 processes in Mesos UI in a matter of seconds! This was expected. Code inside Docker container was executing successfully and exiting normally. And since it exited, Marathon made sure that another instance of the app was started immediately.
On the other hand, when I start an nginx container which keeps listening on port 80, it becomes a long running task and a new task (Docker container) is spun up only when the existing container exits (successfully or unsuccessfully).
You probably need to work on the CMD section of your Dockerfile. Does the container in question keep running when started normally? That is, without Marathon - just using plain docker run? If yes, check if it keeps running in detached mode - docker run -d. If it exits, then CMD is the part you need to work on.