New system, old problems. Getting Oracle Instant Client to work was a trouble 10 years ago and it’s not different today 🤣

There are two ways to install it on Ubuntu/Debian. First is “recommended”, but boring. Second is “crazy”, but have some benefits. As I’m doing it mostly in Docker images, that’s how I will present it.

First, check for up to date versionsexternal link and links on Oracle’s official site.

For non-RPM based distributions recommended way to install Oracle Instant Client is via ZIP package1. I do it more or less like below:

ZIP Dockerfile example
FROM ubuntu:24.04

ENV OIC_VERSION 21.13.0.0.0
ENV ORACLE_HOME /usr/lib/oracle/21/client64
ENV PATH $ORACLE_HOME/lib:$PATH

RUN apt-get update && \
    apt-get install -y \
        curl \
        unzip \
        libaio1t64 \
        libnsl2 && \
    mkdir -p /opt/oracle && \
    curl -fsSLo instantclient-basic-linux.x64-${OIC_VERSION}dbru.zip \
        https://download.oracle.com/otn_software/linux/instantclient/2113000/instantclient-basic-linux.x64-${OIC_VERSION}dbru.zip && \
    unzip instantclient-basic-linux.x64-${OIC_VERSION}dbru.zip -d /opt/oracle && \
    curl -fsSLo instantclient-sqlplus-linux.x64-${OIC_VERSION}dbru.zip \
        https://download.oracle.com/otn_software/linux/instantclient/2113000/instantclient-sqlplus-linux.x64-${OIC_VERSION}dbru.zip && \
    unzip instantclient-sqlplus-linux.x64-${OIC_VERSION}dbru.zip -d /opt/oracle && \
    rm -f instantclient*.zip && \
    # 21 from version
    export OIC_MAJOR=$(printf $OIC_VERSION | cut -d. -f1) && \
    echo $OIC_MAJOR && \
    # 13 from version
    export OIC_MINOR=$(printf $OIC_VERSION | cut -d. -f2) && \
    echo $OIC_MINOR && \
    # put it where you normally expect it
    mkdir -p $ORACLE_HOME && \
    ln -s /opt/oracle/instantclient_${OIC_MAJOR}_${OIC_MINOR} $ORACLE_HOME/lib && \
    # we might beed to manually fix some lib paths
    ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1 && \
    # update ld.so cache
    echo $ORACLE_HOME/lib > /etc/ld.so.conf.d/oracle-instantclient.conf && \
    ldconfig && \
    # remove packages we don't need anymore
    apt-get purge -y \
        curl \
        unzip && \
    apt-get autoremove -y && \
    # let test if it works
    sqlplus -version

There are few weird looking things here.

  1. ZIP packages provides binaries and libraries in the same directory, usually named instantclient_XX_YY. To make binaries (eg. sqlplus) available for execution, we can either add this whole directory to the PATH or symlink binaries one by one… PATH wins because it’s simpler, but I don’t like it. Feels dirty.
  2. ZIP packages provide few symlinks for libraries, but you still might need to add few more to get it working.
  3. I don’t like to manually amend stuff directly in the /usr/lib. There’s a /usr/local/lib for that.
  4. You have to remember about ls.so cache. Overwriting LD_LIBRARY_PATH especially in Docker containers might cause troubles to load libraries from other locations. Adding proper config in /etc/ld.so.conf.d saves a lot of pain.

Installation from RPM (yes, on Ubuntu!)

It’s one of those crazy ideas, but surprisingly it have non obvious benefits. RPM packages have better structured file localizations (separate bin and lib). DEBs generated from RPM packages can be uploaded to the Nexus/Artifactory which would save developers trouble to fetch proper binaries each time.

Warning

The only reason for me to go this way is to upload DEB packages to the central repo that I’m in control of. If you don’t have it, go with the ZIP packages.

Let me demonstrate how to achieve it with a Docker builder pattern.

RPM Dockerfile example
FROM ubuntu:24.04 as builder

WORKDIR /tmp

RUN apt-get update && \
    apt-get install -y alien

ENV OIC_VERSION 21.13.0.0.0-1

RUN curl -fsSLo oracle-instantclient-basic-${OIC_VERSION}.el8.x86_64.rpm \
        https://download.oracle.com/otn_software/linux/instantclient/2113000/oracle-instantclient-basic-${OIC_VERSION}.el8.x86_64.rpm && \
    curl -fsSLo oracle-instantclient-sqlplus-${OIC_VERSION}.el8.x86_64.rpm \
        https://download.oracle.com/otn_software/linux/instantclient/2113000/oracle-instantclient-sqlplus-${OIC_VERSION}.el8.x86_64.rpm

RUN alien *.rpm


FROM ubuntu:24.04

# so I won't need to purge them manually
VOLUME /var/lib/apt/lists /var/cache/apt/archives

ENV ORACLE_HOME /usr/lib/oracle/21/client64
ENV PATH $ORACLE_HOME/bin:$PATH

COPY --from=builder /tmp/*.deb /tmp/

RUN apt-get update && \
    apt-get install -y \
        libaio1t64 \
        libnsl2 && \
    dpkg -i /tmp/oracle*.deb && \
    rm -f /tmp/oracle*.deb && \
    # we might beed to manually fix some lib paths
    ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1 && \
    # let test if it works
    sqlplus -version

If with ZIP package there were “few weird things”, there’s much more weird stuff here 🤣

Warning

You have to carefully select the version of RPM to be binary compatible with your distribution. Check for glibc version or just experiment.

Why to even go this path? Because if you’re able to provide packages from central repository, the whole things became much simpler for users, less vulnerable to the outside changes (like Oracle changing/dropping URLs). Just take a look:

Central repo example
FROM ubuntu:24.04

# so I won't need to purge them manually
VOLUME /var/lib/apt/lists /var/cache/apt/archives

ENV ORACLE_HOME /usr/lib/oracle/21/client64
ENV PATH $ORACLE_HOME/bin:$PATH

RUN apt-get update && \
    apt-get install -y \
        oracle-instantclient-basic_21.13.0.0.0 \
        oracle-instantclient-sqlplus_21.13.0.0.0 \
        libaio1t64 \
        libnsl2 && \
    ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1 && \
    sqlplus -version

And that’s it! It’s about half of the size comparing to the first example with ZIP packages, but still crazy 😉

Having environment prepared like that, you can install now Python with cx-oracle2 or even better oracledb3.


Enjoyed? Buy Me a Coffee at ko-fi.com