Duplication in Dockerfiles - docker

I have a Django Web-Application that uses celery in the background for periodic tasks.
Right now I have three docker images
one for the django application
one for celery workers
one for the celery scheduler
whose Dockerfiles all look like this:
FROM alpine:3.7
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
COPY Pipfile Pipfile.lock ./
RUN apk update && \
apk add python3 postgresql-libs jpeg-dev git && \
apk add --virtual .build-deps gcc python3-dev musl-dev postgresql-dev zlib-dev && \
pip3 install --no-cache-dir pipenv && \
pipenv install --system && \
apk --purge del .build-deps
COPY . ./
# Run the image as a non-root user
RUN adduser -D noroot
USER noroot
EXPOSE $PORT
CMD <Different CMD for all three containers>
So they are all exactly the same except the last line.
Would it make sense here to create some kind of base image that contains everything except CMD. And all three images use that as base and add only their respective CMD?
Or won't that give me any advantages, because everything is cached anyway?
Is a seperation like you see above reasonable?
Two small bonus questions:
Sometimes the apk update .. layer is cached by docker. How does docker know that there are no updates here?
I often read that I should decrease layers as far a possible to reduce image size. But isn't that against the caching idea and will result in longer builds?

I will suggest to use one Dockerfile and just update your CMD during runtime. Litle bit modification will work for both local and Heroku as well.
As far Heroku is concern they provide environment variable to start container with the environment variable.
heroku set-up-your-local-environment-variables
FROM alpine:3.7
ENV PYTHONUNBUFFERED 1
ENV APPLICATION_TO_RUN=default_application
RUN mkdir /code
WORKDIR /code
COPY Pipfile Pipfile.lock ./
RUN apk update && \
apk add python3 postgresql-libs jpeg-dev git && \
apk add --virtual .build-deps gcc python3-dev musl-dev postgresql-dev zlib-dev && \
pip3 install --no-cache-dir pipenv && \
pipenv install --system && \
apk --purge del .build-deps
COPY . ./
# Run the image as a non-root user
RUN adduser -D noroot
USER noroot
EXPOSE $PORT
CMD $APPLICATION_TO_RUN
So When run you container pass your application name to run command.
docker run -it --name test -e APPLICATION_TO_RUN="celery beat" --rm test

I would recommend looking at docker-compose to simplify management of multiple containers.
Use a single Dockerfile like the one you posted above, then create a docker-compose.yml that might look something like this:
version: '3'
services:
# a django service serving an application on port 80
django:
build: .
command: python manage.py runserver
ports:
- 8000:80
# the celery worker
worker:
build: .
command: celery worker
# the celery scheduler
scheduler:
build: .
command: celery beat
Of course, modify the commands here to be whatever you are using for your currently separate Dockerfiles.
When you want to rebuild the image, docker-compose build will rebuild your container image from your Dockerfile for the first service, then reuse the built image for the other services (because they already exist in the cache). docker-compose up will spin up 3 instances of your container image, but overriding the run command each time.
If you want to get more sophisticated, there are plenty of resources out there for the very common combination of django and celery.

Related

How to prevent having to rebuild image on code changes

I started using Docker for a personal project and realized that this increases my development time to an unnacceptable amount. I would rather spin up an LXC instance if I had to rebuild images for every code change.
I heard there was a way to mount this but wasn't sure exactly how one would go about it. I also have a docker compose yaml file but I think you mount a volume or something in the Dockerfile? The goal is to have code changes not need to rebuild a container image.
FROM ubuntu:18.04
EXPOSE 5000
# update apt
RUN apt-get update -y
RUN apt-get install -y --no-install-recommends build-essential gcc wget
# pip installs
FROM python:3.10
# TA-Lib
RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \
tar -xvzf ta-lib-0.4.0-src.tar.gz && \
cd ta-lib/ && \
./configure && \
make && \
make install
RUN rm -R ta-lib ta-lib-0.4.0-src.tar.gz
ADD app.py /
RUN pip install --upgrade pip setuptools
RUN pip install pymysql
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
RUN pip freeze >> /tmp/requirement.txt
COPY . /tmp
CMD ["python", "/tmp/app.py"]
RUN chmod +x ./tmp/start.sh
RUN ./tmp/start.sh
version: '3.8'
services:
db:
image: mysql:8.0.28
command: '--default-authentication-plugin=mysql_native_password'
restart: always
environment:
- MYSQL_DATABASE=#########
- MYSQL_ROOT_PASSWORD=####
# client:
# build: client
# ports: [3000]
# restart: always
server:
build: server
ports: [5000]
restart: always
Here's what I would suggest to make dev builds faster:
Bind mount code into the container
A bind mount is a directory shared between the container and the host. Here's the syntax for it:
version: '3.8'
services:
# ... other services ...
server:
build: server
ports: [5000]
restart: always
volumes:
# Map the server directory in into the container at /code
- ./server:/code
The first part of the mount, ./server is relative to the directory that the docker-compose.yml file is in. If the server directory and the docker-compose.yml file are in different directories, you'll need to change this part.
After that, you'd remove the part of the Dockerfile which copies code into the container. Something like this:
# pip installs
FROM python:3.10
# TA-Lib
RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \
tar -xvzf ta-lib-0.4.0-src.tar.gz && \
cd ta-lib/ && \
./configure && \
make && \
make install
RUN rm -R ta-lib ta-lib-0.4.0-src.tar.gz
RUN pip install --upgrade pip setuptools
RUN pip install pymysql
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
CMD ["python", "/code/app.py"]
The advantage of this approach is that when you hit 'save' in your editor, the change will be immediately propagated into the container, without requiring a rebuild.
Documentation on syntax
Note about production builds: I don't recommend bind mounts when running your production server. In that case, I would recommend copying your code into the container instead of using a bind mount. This makes it easier to upgrade a running server. I typically write two Dockerfiles and two docker-compose.yml files: one set for production, and one set for development.
Install dependencies before copying code into container
One part of your Dockerfile is causing most of the slowness. It's this part:
ADD app.py /
# ... snip two lines ...
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
This defeats Docker's layer caching. Docker is capable of caching layers, and using the cache if nothing in that layer has changed. However, if a layer changes, any layer after that change will be rebuilt. This means that changing app.py will cause the pip install --requirement /tmp/requirements.txt line to run again.
To make use of caching, you should follow the rule that the least-frequently changing file goes in first, and most-frequently changing file goes last. Since you change the code in your project more often than you change which dependencies you're using, that means you should copy app.py in after you've installed the dependencies.
The Dockerfile would change like this:
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
# After installing dependencies
ADD app.py /
In my projects, I find that rebuilding a container without changing dependencies takes about a second, even if I'm not using the bind-mount trick.
For more information, see the documentation on layer caching.
Remove unused stage
You have two stages in your Dockerfile:
FROM ubuntu:18.04
# ... snip ...
FROM python:3.10
The FROM command means that you are throwing out everything in the image and starting from a new base image. This means that everything in between these two lines is not really doing anything. To fix this, remove everything before the second FROM statement.
Why would you use multistage builds? Sometimes it's useful to install a compiler, compile something, then copy it into a fresh image. Example.
Merge install and remove step
If you want to remove a file, you should do it in the same layer where you created the file. The reason for this is that deleting a file in a previous layer does not fully remove the file: the file still takes up space in the image. A tool like dive can show you files which are having this problem.
Here's how I would suggest changing this section:
RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \
tar -xvzf ta-lib-0.4.0-src.tar.gz && \
cd ta-lib/ && \
./configure && \
make && \
make install
RUN rm -R ta-lib ta-lib-0.4.0-src.tar.gz
Merge the rm into the previous step:
RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \
tar -xvzf ta-lib-0.4.0-src.tar.gz && \
cd ta-lib/ && \
./configure && \
make && \
make install && \
cd .. && \
rm -R ta-lib ta-lib-0.4.0-src.tar.gz

Setting up NSCA in Docker Alpine image for passive nagios check

In the Alpine linux package site https://pkgs.alpinelinux.org/packages
NSCA packages are yet to get added. Is there an alternative to setup NSCA in Alpine Linux for passive-check?
If there is no package for it, you can always build it yourself.
FROM alpine AS builder
ARG NSCA_VERSION=2.9.2
RUN apk update && apk add build-base build-base gcc wget git
RUN wget http://prdownloads.sourceforge.net/nagios/nsca-$NSCA_VERSION.tar.gz
RUN tar xzf nsca-$NSCA_VERSION.tar.gz
RUN cd nsca-$NSCA_VERSION&& ./configure && make all
RUN ls -lah nsca-$NSCA_VERSION/src
RUN mkdir -p /dist/bin && cp nsca-$NSCA_VERSION/src/nsca /dist/bin
RUN mkdir -p /dist/etc && cp nsca-$NSCA_VERSION/sample-config/nsca.cfg /dist/etc
FROM alpine
COPY --from=builder /dist/bin/nsca /bin/
COPY --from=builder /dist/etc/nsca.cfg /etc/
Since this is using multiple stages, your resulting image will not contain development files and will still be small.

Docker node js issues: sudo not found

I am having issues with building my docker file with Azure DevOps.
Here is a copy of my docker file:
FROM node:10-alpine
# Create app directory
WORKDIR /usr/src/app
# Copy app
COPY . .
# install packages
RUN apk --no-cache --virtual build-dependencies add \
git \
python \
make \
g++ \
&& sudo npm#latest -g wait-on concurrently truffle \
&& npm install \
&& apk del build-dependencies \
&& truffle compile --all
# Expose the right ports, the commands below are irrelevant when using a docker-compose file.
EXPOSE 3000
CMD ["npm", "run", "server”]
it was working recently now I am getting the following error message:
sudo not found.
What is the cause of this sudo not found error?
Don't use sudo. Just drop that from the command. That image is already running as root by default - there's no reason for it.
TJs-MacBook-Pro:~ tj$ docker run node:10-alpine whoami
root

Running redis on nodejs Docker image

I have a Docker image which is a node.js application. The app retrieves some configuration value from Redis which is running locally. Because of that, I am trying to install and run Redis within the same container inside the Docker image.
How can I extend the Docker file and configure Redis in it?
As of now, the Dockerfile is as below:
FROM node:carbon
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
EXPOSE 3011
CMD node /app/src/server.js
The best solution would be to use docker compose. With this you would create a redis container, link to it then start your node.js app. First thing would be to install docker compose detailed here - (https://docs.docker.com/compose/install/).
Once you have it up and running, You should create a docker-compose.yml in the same folder as your app's dockerfile. It should contain the following
version: '3'
services:
myapp:
build: .
ports:
- "3011:3011"
links:
- redis:redis
redis:
image: "redis:alpine"
Then redis will be accessible from your node.js app but instead of localhost:6379 you would use redis:6379 to access the redis instance.
To start your app you would run docker-compose up, in your terminal. Best practice would be to use a network instead of links but this was made for simplicity.
This can also be done as desired, having both redis and node.js on the same image, the following Dockerfile should work, it is based off what is in the question:
FROM node:carbon
RUN wget http://download.redis.io/redis-stable.tar.gz && \
tar xvzf redis-stable.tar.gz && \
cd redis-stable && \
make && \
mv src/redis-server /usr/bin/ && \
cd .. && \
rm -r redis-stable && \
npm install -g concurrently
EXPOSE 6379
WORKDIR /app
COPY package.json /app
RUN npm install
COPY . /app
EXPOSE 3011
EXPOSE 6379
CMD concurrently "/usr/bin/redis-server --bind '0.0.0.0'" "sleep 5s; node /app/src/server.js"
This second method is really bad practice and I have used concurrently instead of supervisor or similar tool for simplicity. The sleep in the CMD is to allow redis to start before the app is actually launched, you should adjust it to what suits you best. Hope this helps and that you use the first method as it is much better practice
My use case was to add redis server in alpine tomcat flavour:
So this worked:
FROM tomcat:8.5.40-alpine
RUN apk add --no-cache redis
RUN apk add --no-cache screen
EXPOSE 6379
EXPOSE 3011
## Run Tomcat
CMD screen -d -m -S Redis /usr/bin/redis-server --bind '0.0.0.0' && \
${CATALINA_HOME}/bin/catalina.sh run
EXPOSE 8080
If you are looking for a bare minimum docker with nodejs and redis-server, this works :
FROM nikolaik/python-nodejs:python3.5-nodejs8
RUN apt-get update
apt-get -y install redis-server
COPY . /app
WORKDIR /app
nohup redis-server &> redis.log &
and then you can have further steps for your node application.

Unable to download docker golang image: No command specified

Newbie in docker here.
I want to build a project using go language and my docker-compose.yml file has the following:
go:
image: golang:1.7-alpine
volumes:
- ./:/server/http
ports:
- "80:8080"
links:
- postgres
- mongodb
- redis
environment:
DEBUG: 'true'
PORT: '8080'
When I run docker-compose up -d in terminal, it returns the following error:
`ERROR: for go Cannot create container for service go: No command specified`
How should I fix it?
Golang:1.7-alpine is just a basis for building a Go container, and does not have a CMD or an ENTRYPOINT, so ends immediately.
Use an image doing really something, like printing hello world every 45 seconds
I solved it by using golang:1.7 instead of golang:1.7-alpine.
You should run your container with an argument which will be passed to the default ENTRYPOINT, to be executed as a command
But the best practice these days is to use multistage, in order to generate a smaller image with just your application.
Or you can define your ENTRYPOINT being your build Go application.
See 'Using Docker Multi-Stage Builds with Go ', using the new AS build-stage keyword.
Your Dockerfile would be:
# build stage
ARG GO_VERSION=1.8.1
FROM golang:${GO_VERSION}-alpine AS build-stage
MAINTAINER fbgrecojr#me.com
WORKDIR /go/src/github.com/frankgreco/gobuild/
COPY ./ /go/src/github.com/frankgreco/gobuild/
RUN apk add --update --no-cache \
wget \
curl \
git \
&& wget "https://github.com/Masterminds/glide/releases/download/v0.12.3/glide-v0.12.3-`go env GOHOSTOS`-`go env GOHOSTARCH`.tar.gz" -O /tmp/glide.tar.gz \
&& mkdir /tmp/glide \
&& tar --directory=/tmp/glide -xvf /tmp/glide.tar.gz \
&& rm -rf /tmp/glide.tar.gz \
&& export PATH=$PATH:/tmp/glide/`go env GOHOSTOS`-`go env GOHOSTARCH` \
&& glide update -v \
&& glide install \
&& CGO_ENABLED=0 GOOS=`go env GOHOSTOS` GOARCH=`go env GOHOSTARCH` go build -o foo \
&& go test $(go list ./... | grep -v /vendor/) \
&& apk del wget curl git
# production stage
FROM alpine:3.5
MAINTAINER fbgrecojr#me.com
COPY --from=build-stage /go/src/github.com/frankgreco/go-docker-build/foo .
ENTRYPOINT ["/foo"]

Resources