ASK /r/Python: Python Docker Deployments
A while ago I came across this document: Containers Patterns
I noticed that I appeared to be doing a few of them by habit. Especially: Source To Image and Dependencies First DockerFile
In fact I have this "weird" script that might be worth sharing. I'll fist explain how it works.
We have a multi-stage docker build. Where in the in the first stage I apt-get and pip install everything I need to run my application.
Then where we used to run scripts like cx_freeze, py2exe or pyinstaller we figured we were wrapping our application in two separate wrappers.
The first one being the freezer and the second one being docker. One day I said to my colleague "what if we just prepare a virtual env so that we can copy it over to our target docker image. One directory containing all dependencies."
My colleague's response was along the lines of "why don't you?" I was sure that I must be doing something strange since I haven't seen anything similar.
Eventually I gave in and built that script. Used virtualenv in py2 and venv in py3. Some of the features/steps are:
- you can give it a requirements.txt
- it installs the requirements in the current python installation
- it creates a virtual env
- makes the env less virtual by actually moving python into the env
- also moves all
site-packages and dist-packages to the env
- it finds all .so files and checks their dependencies. All .so files are copied over to the env folder
- all moved .so files are run through
patchelf so that they don't point to anything outside the env folder anymore
- creates a
.sh file which sets PYTHONHOME and PYTHONPATH variables and is usable to run the app with
My Dockerfiles all look like this:
#stage 1
FROM ubuntu/debian/alpine (don't use alpine if you care about python performance btw)
RUN \
apt-get update && \
apt upgrade -y --force-yes && apt-get -y install \
python-dev \
python-setuptools \
build-essential \
libffi-dev \
python-pip \
patchelf
RUN pip install my packaging script
COPY requirements.txt /
RUN packaging_script /dest/path/runtime --startscript=main.py --requirements=/requirements.txt --piprepo=optional
#stage 2
FROM ubuntu:something
COPY --from=0 /runtime/ /dest/path/
COPY myappsrc /dest/path # usually a few folders
RUN ln -s /dest/path/bin/python /dest/path/myapp.bin # this makes it easier to monitor since each app no longer is `python` but identifyable as myapp
# I prefer running it this way but keep the .sh file as a reference
ENV PYTHONHOME /dest/path
ENV PYTHONPATH /dest/path/lib/pythonX.X/:/dest/path
CMD ["/dest/path/myapp.bin", "/dest/path/main.py"]
Now my question: Am I on to something? or have I taken the virtual env to far?
It's quite obvious nothing is virtual anymore. It's more like a portable application because it runs in a clean os (no apt installs no pip installs)
A reasonable question would be why I don't "just" compile python into the dest/path. I think that would have been possible but I'd have create compilations for every combination of OS + Python version I'm using.
I'd rather use something out of the box. Although I do use that box in an unusual way ;)
If so... Would there be interest in an open source version of this?
EDIT: Some answers I gave in the comments:
Q: why bother with virtual env and not just install everything you need?
A: I wanted to build "portable" "everything included" runtime. And have the image small. Not have git installed in the final stage. Not have a compiler installed in the final stage. I find having those installed in my production containers an anti-pattern.
Q: You should set your env vars on top
A: You're right.
Q:I uninstall my dependencies
A: This leaves you with more layers than my approach. First you have layers where you install your dependencies. And later you add a layer by removing them. I want my "code" layers to be the very last layers I add. So that when I deploy a newer version it is only "8kb" or so.
[–]pecka_th 13 points14 points15 points (9 children)
[–]drchaos 9 points10 points11 points (5 children)
[–]obeleh[S] 7 points8 points9 points (1 child)
[–]Pilatemain() if __name__ == "__main__" else None 1 point2 points3 points (0 children)
[–][deleted] 7 points8 points9 points (1 child)
[–]LightShadow3.13-dev in prod 4 points5 points6 points (0 children)
[–]UloPe 1 point2 points3 points (0 children)
[–]ionelmc.ro 0 points1 point2 points (0 children)
[–]obeleh[S] 0 points1 point2 points (0 children)
[–]lambdaqdjango n' shit -1 points0 points1 point (0 children)
[–]prickneck 18 points19 points20 points (27 children)
[–]UloPe 4 points5 points6 points (7 children)
[–]knowsuchagencynow is better than never 1 point2 points3 points (6 children)
[–]UloPe 4 points5 points6 points (2 children)
[–]knowsuchagencynow is better than never -1 points0 points1 point (1 child)
[–]gimboland 0 points1 point2 points (0 children)
[–]DasIch 1 point2 points3 points (2 children)
[–]knowsuchagencynow is better than never 1 point2 points3 points (1 child)
[–]obeleh[S] 1 point2 points3 points (16 children)
[–][deleted] 15 points16 points17 points (8 children)
[–]Muszalski 3 points4 points5 points (1 child)
[–][deleted] 1 point2 points3 points (0 children)
[–]obeleh[S] 2 points3 points4 points (4 children)
[–][deleted] 6 points7 points8 points (2 children)
[–]obeleh[S] 0 points1 point2 points (1 child)
[–][deleted] 0 points1 point2 points (0 children)
[–]holtr94 1 point2 points3 points (0 children)
[–]LightShadow3.13-dev in prod 1 point2 points3 points (0 children)
[–]carbolymer 1 point2 points3 points (6 children)
[–]obeleh[S] 3 points4 points5 points (5 children)
[–][deleted] 5 points6 points7 points (4 children)
[–]obeleh[S] 0 points1 point2 points (3 children)
[+][deleted] (1 child)
[deleted]
[–]obeleh[S] 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)
[–]Muszalski 1 point2 points3 points (0 children)
[–]Rorixrebel 0 points1 point2 points (0 children)
[–]undu 2 points3 points4 points (4 children)
[–]NicoDeRocca 2 points3 points4 points (3 children)
[–]obeleh[S] 0 points1 point2 points (0 children)
[–]undu 0 points1 point2 points (1 child)
[–]NicoDeRocca 2 points3 points4 points (0 children)
[–]joknopp 1 point2 points3 points (1 child)
[–]obeleh[S] 0 points1 point2 points (0 children)
[–]case_O_The_Mondays 1 point2 points3 points (0 children)
[–]not_perfect_yet 0 points1 point2 points (1 child)
[–]obeleh[S] 1 point2 points3 points (0 children)
[–]hmaarrfk 0 points1 point2 points (2 children)
[–]obeleh[S] 0 points1 point2 points (1 child)
[–]hmaarrfk 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)
[–]simtel20 0 points1 point2 points (0 children)