Docker Community Forums

Share and learn in the Docker community.

Multi Stage Build - Need SQLite and Flask along with a few other python libraries

Howdy all, using two FROM statements in Dockerfile is that a good idea?

FROM ubuntu:bionic
RUN apt-get -y update
RUN apt-get -y upgrade
RUN apt-get install -y sqlite3 libsqlite3-dev
RUN mkdir /db
RUN /usr/bin/sqlite3 /db/app.db

FROM python
ADD refreshgamedata.py /
ADD static /static
ADD images /images
ADD templates /templates
ADD app.py /
ADD requirements.txt /
RUN pip install -r requirements.txt
RUN python refreshgamedata.py
CMD python app.py

requirements.txt

Flask
pandas
lxml
requests
sqlalchemy

Can this be done better?

You should start with “make it right” before you head into optimization.
If this Dockerfile is what you concluded after reading the documentation, I highly recommend to read it again…

Gulp

Okay.

This was a Dockerfile, I pieced together from a couple of examples I found on installing SQLite and Flask. And, it does work atleast for my test case. I admit I just started researching and learning Docker last friday. But okay, I will do more digging through the docs.

This is functioning for my small personal micro-project. But of course, learing the “right” was is what I am after.

Thanks for your feedback, meyay.

The second sentence emphases what staged builds are actualy used for:

With multi-stage builds, you use multiple FROM statements in your Dockerfile. Each FROM instruction can use a different base, and each of them begins a new stage of the build. You can selectively copy artifacts from one stage to another, leaving behind everything you don’t want in the final image.

Multi staged builds are realy just to perform the heavy lifting, like compiling an application, into a previous stage, so that you can simply copy the compiled artefact(s) into your final image. Appart from that there is no coupling between the stages.

1 Like

Okay… I understand better now. I have eliminated the second FROM and installed python the with apt-get.

Be safe and stay healthy.

@meyay Thanks.

1 Like

Now we come to the optimziation part.
Each instrution in the Dockerfile will create a new image layer. Thus try to chain as many commands as possible and COPY/ADD as man files in a single action as possible.

Your example transformed would look like this:

As long as you don’t want to implicitly extract a tar file during the ADD instruction, use its lightweight alternative COPY (which realy just copies). If possible create a subfolder and move all folder and files into this subfolder, this will allow to copy the content of the subfolder to the root of the container.

1 Like

Take a look at the article here, on “Multi-stage builds”.

From single to multi-package
When last we left our application, the directory structure looked something like this:

flask-tracking/
flask_tracking/
templates/ # Holds Jinja templates
init.py # General application setup
forms.py # User data to domain data mappers and validators
models.py # Domain models
views.py # well … controllers, really.
config.py # Configuration, just like it says on the cover
README.md
requirements.txt
run.py # python run.py to bring the application up locally.
To keep things clear, let’s move the existing forms, models, and views into a tracking sub-package and create another sub-package for our User-specific functionality which we will call users:

flask_tracking/
templates/
tracking/ # This is the code from Part 1
init.py # Create this file - it should be empty.
forms.py
models.py
views.py
users/ # Where we are working today
init.py
init.py # This is also code from Part 1
This means that we will need to change our import in flask_tracking/init.py from from .views import tracking to from .tracking.views import tracking.

Then there is the database setup in tracking.models. This we will move out into the parent package (flask_tracking) since the database manager will be shared between packages. Let’s call that module data:

flask_tracking/data.py

from flask.ext.sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def query_to_list(query, include_field_names=True):
“”“Turns a SQLAlchemy query into a list of data values.”""
column_names =
for i, obj in enumerate(query.all()):
if i == 0:
column_names = [c.name for c in obj.table.columns]
if include_field_names:
yield column_names
yield obj_to_list(obj, column_names)

def obj_to_list(sa_obj, field_order):
“”“Takes a SQLAlchemy object - returns a list of all its data”""
return [getattr(sa_obj, field_name, None) for field_name in field_order]
Then we can update tracking.models to use from flask_tracking.data import db and tracking.views to use from flask_tracking.data import db, query_to_list and we should now have a working multi-package application.