TL;DR: Don't use alpine images. There's a glibc issue.

We added a cx-oracle dependency to one of our django apps. As the previous image was just an alpine basic (python) image we needed to put in place a process to build the new image.

Initially, I've tried to use the same alpine image (oh, the slimmed down size is sooo tempting), but then I've hit a bunch of "Unknown symbol" messages when I've ran the install of cx-oracle in the docker build process. Subsequently, I've had to switch to python:3.6-slim-jessie.

The dependencies

Oracle doesn't play nice with automated downloads (you need to accept licenses etc.), so it's advisable to download the instaclient binaries and store them locally for consumption. The minimum set of dependencies required for cx-oacle I found to be:

  • instaclinet-basic
  • instaclient-odbc
  • instaclient-sdk
  • instaclient-sqlplus
  • instaclient-tools

To these, you need to add the suffix for platform and version. In my case, it is linux.64-12.2.0.1.0.zip

The Dockerfile

We start the Dockerfile with the base image and the maintainer:

# Set the base image to use to Ubuntu
FROM python:3.6-slim-jessie

# Set the file maintainer (your name - the file's author)
LABEL maintainer="[email protected]"

We also have a corporate proxy to go through:

# Begin PROXY change
ENV http_proxy 'http://user:[email protected]:8088'
ENV https_proxy 'http://user:[email protected]:8088'
# End PROXY change

Then we define some environment variables:

# Set env variables used in this Dockerfile (add a unique prefix, such as DOCKYARD)
# Local directory with project source
# Directory in container for all project files
ENV APP_SRC=middletier
ENV APP_SRVHOME=/srv
ENV APP_SRVPROJ=/srv/middletier

And we prepare the work directory:

# Create application subdirectories
WORKDIR $APP_SRVHOME
RUN mkdir media static logs
VOLUME ["$APP_SRVHOME/media/", "$APP_SRVHOME/logs/"]

We copy the docker-entrypoint.sh, the django sources and the oracle instaclinet binaries:

# Copy application source code to SRCDIR
COPY ./docker-entrypoint.sh /
COPY ./requirements.txt $APP_SRVHOME/
COPY $APP_SRC $APP_SRVPROJ
COPY ./oracle-instantclient ${APP_SRVHOME}/oracle

We update the apt indeices and install dependent libaio1:

# Update the default application repository sources list
RUN apt-get update
RUN apt-get install libaio1

We then unpack the binaries and install them:

RUN apt-get install unzip && \
    cd "${APP_SRVHOME}/oracle" && \
    mkdir -p /opt/oracle && \
    for i in *; do \
      unzip $i ; \
    done && \
    apt-get remove --purge -y unzip && \
    mv instantclient_12_2 /opt/oracle && \
    cd / && \
    rm -rf "${APP_SRVHOME}/oracle" && \
    cd /opt/oracle/instantclient_12_2 && \
    ln -s libclntshcore.so.12.1 libclntshcore.so && \
    ln -s libclntsh.so.12.1 libclntsh.so && \
    ln -s libocci.so.12.1 libocci.so && \
    ln -s libsqora.so.12.1 libsqora.so

We set up the instaclient environment variables:

ENV ORACLE_BASE /opt/oracle/instantclient_12_2
ENV LD_LIBRARY_PATH /opt/oracle/instantclient_12_2
ENV TNS_ADMIN /opt/oracle/instantclient_12_2
ENV ORACLE_HOME /opt/oracle/instantclient_12_2

We install the necessary python requirements:

RUN pip install --no-cache-dir --upgrade pip && \
      pip install --no-cache-dir virtualenv && \
      virtualenv djangoenv && \
      . $APP_SRVHOME/djangoenv/bin/activate && \
      pip install --no-cache-dir gunicorn && \
      pip install --no-cache-dir -r $APP_SRVPROJ/requirements.txt

We make sure the startup script is UNIX-terminated:

# Convert to unix the script
RUN apt-get install dos2unix  && \
    dos2unix /docker-entrypoint.sh  && \
    apt-get remove --purge -y dos2unix

Then, we finish the configuration:

EXPOSE 8000
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]

The docker-entrypoint.sh is a simple file starting up the django server on port 8000. It can be as simple as running python manage.py runserver 8000. We found gunicorn more performant though :)

Footnotes

  • This describes a way to do this dependency. Alternatives can include volume-mounted binaries, building an intermediarry image with the binaries (we do this for another app with numpy).
  • As each docker command creates another layer, you may want to squish multiple commands (like the ones unpacking instaclient and the python installation) into a single one.
  • Check the various licenses!

HTH,