I am dockerizing my Django application with docker multi-stage build. Now am facing an issue with dependencies
Dockerfile
FROM python:3.8-slim-buster AS base
WORKDIR /app
RUN python -m venv venv
ENV PATH="/app/venv:$PATH"
COPY requirements.txt .
RUN pip install -r requirements.txt \
&& pip install gunicorn
COPY entrypoint.sh .
COPY . .
FROM python:3.8-slim-buster
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
COPY --from=base /app /app/
ENV PATH="/app/venv:$PATH"
ENTRYPOINT sh entrypoint.sh
When running the container it raises import error.
ImportError: Couldn't import Django. Are you sure it's installed and available on your PYTHONPATH environment variable? Did you forget to activate a virtual environment?
I went to the same situation few month ago and it was a conflict between 2 packages, so django was not installed during the pip install.
You can add on your docker build command the '--progress=plain' option and see if everything is ok, during the docker build :
$ docker build --no-cache --progress=plain -t my_service_name .
Baptiste.
here is a working multistage docker build for django.
ENV PYTHONBUFFERED 1
WORKDIR /opt/webapp/
ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# copy requirements.txt
COPY ./requirements.txt /opt/webapp/requirements.txt
RUN pip3 install -r requirements.txt --no-cache-dir
# runner stage
FROM python:3.8-slim-buster AS runner
ARG SECRET_KEY
ARG DEBUG
WORKDIR /opt/webapp
RUN groupadd -r django \
&& useradd -d /opt/webapp -r -g django django \
&& chown django:django -R /opt/webapp
USER django
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY --chown=django:django . /opt/webapp/
# run the server
CMD gunicorn conf.wsgi:application --bind 0.0.0.0:8000
Related
I get an image of 292 MB with a multi-stage build, compared to 235 MB with a one-stage build. Can anyone help me understand why?
Here are the Dockerfiles.
One-stage build:
# syntax=docker/dockerfile:1
FROM python:3.9.13-alpine3.16
WORKDIR /project
ENV FLASK_APP=run.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run"]
Multi-stage build:
# syntax=docker/dockerfile:1
FROM python:3.9.13-alpine3.16 AS compile-image
RUN apk add --no-cache gcc musl-dev linux-headers
ENV VIRTUAL_ENV=/opt/venv
RUN python -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
COPY requirements.txt .
RUN pip install -r requirements.txt
WORKDIR /project
COPY . .
RUN pip install .
FROM python:3.9.13-alpine3.16
COPY --from=compile-image $VIRTUAL_ENV $VIRTUAL_ENV
ENV FLASK_APP=run.py
ENV FLASK_RUN_HOST=0.0.0.0
EXPOSE 5000
CMD ["flask", "run"]
Note: I used an adapted version of the method suggested in the following article: https://pythonspeed.com/articles/multi-stage-docker-python/.
When you have a second FROM statement in a Dockerfile, everything up until that statement is no longer part of the image, including ENV statements.
So in your second part, VIRTUAL_ENV doesn't have a value. That leads to your COPY statement being COPY --from=compile-image with no paths. I tried looking at the docs and it is a valid statement, but the docs don't describe what happens when you do that. I tested it and it seems that it copies everything from the compile-image into your new image. That causes the image to double in size.
To fix it, you can replace the environment variable in the second part with the path you want, like this
# syntax=docker/dockerfile:1
FROM python:3.9.13-alpine3.16 AS compile-image
RUN apk add --no-cache gcc musl-dev linux-headers
ENV VIRTUAL_ENV=/opt/venv
RUN python -m venv $VIRTUAL_ENV
COPY requirements.txt .
RUN pip install -r requirements.txt
WORKDIR /project
COPY . .
RUN pip install .
FROM python:3.9.13-alpine3.16
COPY --from=compile-image /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
ENV FLASK_APP=run.py
ENV FLASK_RUN_HOST=0.0.0.0
EXPOSE 5000
CMD ["flask", "run"]
Since you probably want the PATH to have /opt/venv/bin in it in the final image, I've moved the setting of the PATH to the second stage. If it's in the first stage, it'll also be lost when you hit the second FROM statement.
I'm not a Python expert, but you might also need to move/copy the python -m venv statement to the second stage if that's needed at runtime.
I was able to build in Docker, but when I run I get error:/bin/sh: 1: [gunicorn,:not found
This happens after " docker run the-name-of-my-project " .
I tried suggestions in previous post:
docker build --no-cache the-name-of-my-project (path"the-name-of-my-project" not found )
docker build --no-cache -t the-name-of-my-project ( invalid arguement, build requires exactly 1 arguement).
Any clue on how to deal with this gunicorn error?
Dockerfile code:
FROM python:3.8-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
COPY requirements.txt .
RUN python -m pip install -r requirements.txt
WORKDIR /app
COPY . /app
RUN adduser -u 5678 --disabled-password --gecos "" appuser && chown -
R appuser /app
USER appuser
CMD ["gunicorn", "--bind", "0.0.0.0:undefined",
"ImageProcessingProgram\app_code_and_POST_call\post_call:app"]
Output: error:/bin/sh: 1: [gunicorn,:not found
I've been trying to make the image of flask+postgresql smaller with docker multistage build, but its crashing because its not finding the flask app.py. Any idea why that is happening?
Error:
web_1 | Usage: flask run [OPTIONS]
web_1 |
web_1 | Error: The file/path provided (app.py) does not appear to exist. Please verify the path is correct. If app is not on PYTHONPATH, ensure the extension is .py
first-web-page_web_1 exited with code 2
Dockerfile that's not working:
FROM python:3.7-alpine as compile-image
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY . .
RUN apk update && apk add postgresql-dev gcc musl-dev && pip install -r requirements.txt
FROM python:3.7-alpine AS build-image
COPY --from=compile-image /opt/venv /opt/venv
# Make sure we use the virtualenv:
ENV PATH="/opt/venv/bin:$PATH"
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
CMD ["flask", "run"]
EXPOSE 5000
Dockerfile that's working that is not multistage:
FROM python:3.7-alpine as build
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN apk update && apk add postgresql-dev gcc musl-dev && pip install -r requirements.txt
FROM build
COPY --from=build /code .
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
CMD ["flask", "run"]
EXPOSE 5000
COPY . /code/
Size of the working image is about 130Mb after its uploded to the registry is it even possible to make it smaller then that?
There's a lot going on here.
First, you would typically use two containers for this sort of project: one for the database, and one for the webapp. This comes with a few advantages:
You can use the official postgresql image, rather than rolling your own
You can upgrade one component without shutting down the other (a nice feature of you are making frequent changes to your web app but would like the database to just stay up and running)
You can scale your webapp separately from the database
You can rely on docker to restart a failed service
With respect to your question:
When you COPY . ., you're not copying your application into /opt/venv. It's going into the root directory, /. In your second stage, you do this:
COPY --from=compile-image /opt/venv /opt/venv
You never copy your app into the final image.
Secondly, when you this:
RUN apk update && apk add postgresql-dev gcc musl-dev && pip install -r requirements.txt
You're not install your Python dependencies into your virtual environment, because you've never activated it.
I think you'd be better off using two separate images to start two separate containers (and then using something like docker-compose to manage your application stack).
I've managed to make the image smaller (from 140MiB ~ 24MiB) but not with multistage builds, just by removing the dependence that were needed for postgresql to be installed. If anyone has an idea why its not working with multistage I would appreciate it.
FROM python:3.7-alpine
RUN mkdir /code
ENV FLASK_APP app.py
ENV FLASK_RUN_HOST 0.0.0.0
ARG BUILD_DEPS="gcc musl-dev postgresql-dev"
ARG RUNTIME_DEPS="libpq"
WORKDIR /code
COPY requirements.txt /code/
RUN apk add --no-cache --virtual .build_deps ${BUILD_DEPS} && \
apk add --no-cache ${RUNTIME_DEPS} && \
pip install --no-cache-dir -r requirements.txt && \
apk del .build_deps
CMD ["flask", "run"]
EXPOSE 5000
COPY . /code/
My Dockerfile is:
FROM ubuntu:18.04
RUN apt-get -y update
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get update -y
RUN apt-get install -y python3.7 build-essential python3-pip
ENV LC_ALL C.UTF-8
ENV LANG C.UTF-8
RUN pip3 install pipenv
COPY . /app
WORKDIR /app
RUN pipenv install
EXPOSE 5000
CMD ["pipenv", "run", "python3", "application.py"]
When I do docker build -t flask-sample:latest ., it builds fine (I think).
I run it with docker run -d -p 5000:5000 flask-sample and it looks okay
But when I go to http://localhost:5000, nothing loads. What am I doing wrong?
Why do you need a virtual environment ? Why do you use Ubuntu as base layer:
A simpler approach would be:
Dockerfile:
FROM python:3
WORKDIR /usr/src/
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
ENTRYPOINT FLASK_APP=/usr/src/app.py flask run --host=0.0.0.0
You put in your requirements.txt the desired packages (e.g flask).
Build image:
docker build -t dejdej/flasky:latest .
Start container:
docker run -it -p 5000:5000 dejdej/flasky
If it is mandatory to use virtual environment , you can try it with
venv:
FROM python:2.7
RUN virtualenv /YOURENV
RUN /YOURENV/bin/pip install flask
CMD ["/YOURENV/bin/python", "application.py"]
Short answer:
Your container is running pipenv, not your application. You need to fix the last line.
CMD ["pipenv", "run", "python3", "application.py"] should be only CMD ["python3", "application.py"]
Right answer:
I completely agree that there isn´t any reason to use pipenv. Better solution is replace your Dockfile to use a python image and forget pipenv. You already in a container, no reason to use a enviroment.
I changed my local dev. to Docker. I use the Django framework. For frontend I use the gulp build command to “create” my files. Now I tried a lot, looked into the Cookiecutter and Saleor project but still having issues to install npm in a way that I can call the gulp build command in my Docker container.
I already tried to add:
RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
RUN apt-get update && apt-get install -y \
nodejs \
COPY ./package.json /app/
RUN npm install
While npm is installed, I still can’t run the command gulp build in my Container. It just says gulp is an unknown command. So it seems npm doesn’t install the defined packages in my package.json file. Anyone here who already solved that and can give me some tips?
Dockerfile
# Pull base image
FROM python:3.7
# Define environment variable
ENV PYTHONUNBUFFERED 1
RUN apt-get update && apt-get install -y \
# Language dependencies
gettext \
# In addition, when you clean up the apt cache by removing /var/lib/apt/lists
# it reduces the image size, since the apt cache is not stored in a layer.
&& rm -rf /var/lib/apt/lists/*
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
# Install Python dependencies
RUN pip install pipenv
RUN pipenv install --system --deploy --dev
docker-compose.py
version: '3'
services:
web:
build:
context: .
dockerfile: ./compose/local/django/Dockerfile
env_file: .env
volumes:
- .:/app
ports:
- "8000:8000"
depends_on:
- db
entrypoint: ./compose/local/django/entrypoint.sh
container_name: myproject
db:
image: postgres
ports:
- "5432:5432"
environment:
# Password will be required if connecting from a different host
- POSTGRES_PASSWORD=password
Update 1:
# Pull base image
FROM combos/python_node:3_10
# Define environment variable
ENV PYTHONUNBUFFERED 1
RUN apt-get update && apt-get install -y \
# Language dependencies
gettext \
# In addition, when you clean up the apt cache by removing /var/lib/apt/lists
# it reduces the image size, since the apt cache is not stored in a layer.
&& rm -rf /var/lib/apt/lists/*
# COPY webpack.config.js app.json package.json package-lock.json /app/
# WORKDIR /app
# RUN npm install
# Set the working directory to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
COPY . /app
RUN npm install
# Install Python dependencies
RUN pip install pipenv
RUN pipenv install --system --deploy --dev
Are you sure npm install is being called in the correct directory? You copy package.json to /app but run npm install from an unknown folder. You can try:
COPY ./package.json /app/
RUN cd /app/ && npm install
But I think you'd want to install gulp globally anyway, so you can skip package.json and just use:
RUN npm install -g gulp-cli
This way whatever calls gulp should have it in PATH and not just that specific directory.
Also, if you want to get a Docker image with both Python 3.7 and Node.js 10 already installed, you can use combos/python_node:3.7_10. It's rebuilt daily to contain the latest versions of both images.