QuestDB for Capital Markets?

Learn more

Immutable images when embedding QuestDB Java library and noexec /tmp

QuestDB is the open-source time-series database for demanding workloads—from trading floors to mission control It delivers ultra-low latency, high ingestion throughput, and a multi-tier storage engine. Native support for Parquet and SQL keeps your data portable, AI-ready—no vendor lock-in.

When /tmp is mounted with noexec (a common hardening step), the QuestDB Java library may still try to unpack its native libraries into /tmp and load them from there. On hardened hosts or minimal containers, this fails with UnsatisfiedLinkError or permission errors.

This guide shows how to run QuestDB’s Java library without writing executables to /tmp, by pre-bundling the native libs and pointing QuestDB at them via -Dquestdb.libs.dir. It also documents the fallback of redirecting Java’s temp directory.

Important: If you're using official QuestDB server container images or platform-specific distributions, you do not need to do anything. They already ship the native libraries and set -Dquestdb.libs.dir, so nothing gets unpacked to /tmp. The rest of this guide is only for teams building their own images from scratch or embedding the QuestDB Java library in another application.


TL;DR

On official QuestDB images/distributions? You're done. This is already handled.

  • QuestDB’s Java library needs two native libs at runtime:
    • libquestdb.so
    • libquestdbr.so
  • They both are already inside the JAR under:
    • io/questdb/bin/linux-x86-64/ (for x86_64)
    • io/questdb/bin/linux-aarch64/ (for ARM64)
  • Best practice: extract those .so files at build time into your image (or host) and start the JVM with:
java -Dquestdb.libs.dir=/opt/questdb/lib -jar your-app.jar
  • Alternative: move Java’s temp dir off /tmp:
java -Djava.io.tmpdir=/run/questdb-tmp -jar your-app.jar

…but pre-bundling is preferable for immutable builds and least surprise in prod.


Who is affected?

This guide applies to teams who embed the QuestDB Java library via Maven/Gradle in a custom application, especially when that application is deployed to a hardened container or host where /tmp is mounted with noexec.


Why this happens

QuestDB ships two native libraries (libquestdb.so, libquestdbr.so) required by the Java client and embedded mode.

When -Dquestdb.libs.dir is not set, the library falls back to unpacking native binaries from its JAR into Java’s temp directory (usually /tmp) and loads them from there. On hardened systems, mapping or executing from /tmp fails.


Ship the native libs with your application and tell QuestDB where to find them.

1) Extract the native libs from the JAR

If you already have the dependency JAR locally, you can extract only the two files you need:

# x86_64 example
unzip -j path/to/questdb-<version>.jar \
'io/questdb/bin/linux-x86-64/libquestdb.so' \
'io/questdb/bin/linux-x86-64/libquestdbr.so' \
-d /opt/questdb/lib
# ARM64 example
unzip -j path/to/questdb-<version>.jar \
'io/questdb/bin/linux-aarch64/libquestdb.so' \
'io/questdb/bin/linux-aarch64/libquestdbr.so' \
-d /opt/questdb/lib

Tip: .so files don’t need the filesystem executable bit, but the mount where they reside must allow executable mappings (avoid noexec). Keep files readable by all, but writable only by the owner.

2) Point QuestDB at your lib directory

At startup, pass:

-Dquestdb.libs.dir=/opt/questdb/lib

Examples:

Plain JVM

java \
-Dquestdb.libs.dir=/opt/questdb/lib \
-cp your-app.jar com.example.Main

Kubernetes (env‑style via JAVA_TOOL_OPTIONS)

env:
- name: JAVA_TOOL_OPTIONS
value: "-Dquestdb.libs.dir=/opt/questdb/lib"

Programmatic (set very early in main)

public static void main(String[] args) {
System.setProperty("questdb.libs.dir", "/opt/questdb/lib");
// start the app before any QuestDB classes trigger native loads
}

Build recipes

A) Docker (multi‑stage)

# --- stage: fetch questdb jar and extract natives
FROM alpine:latest AS qdb-natives
ARG QDB_VERSION=9.0.3
RUN apk add --no-cache unzip ca-certificates
RUN mkdir -p /opt/questdb/lib
ADD https://repo1.maven.org/maven2/org/questdb/questdb/${QDB_VERSION}/questdb-${QDB_VERSION}.jar /tmp/questdb.jar
RUN unzip -j /tmp/questdb.jar \
'io/questdb/bin/linux-x86-64/libquestdb.so' \
'io/questdb/bin/linux-x86-64/libquestdbr.so' \
-d /opt/questdb/lib
# --- stage: your app image
FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=qdb-natives /opt/questdb/lib /opt/questdb/lib
COPY build/libs/your-app-all.jar /app/app.jar
ENV JAVA_TOOL_OPTIONS="-Dquestdb.libs.dir=/opt/questdb/lib"
CMD ["java", "-jar", "/app/app.jar"]

If you build for ARM64, swap the two unzip paths to linux-aarch64.

B) Maven: unpack at build time

Use maven-dependency-plugin to unpack just the native libs into your build output.

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>unpack-questdb-natives</id>
<phase>process-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.questdb</groupId>
<artifactId>questdb</artifactId>
<version>9.0.3</version>
<includes>
io/questdb/bin/linux-x86-64/libquestdb.so,
io/questdb/bin/linux-x86-64/libquestdbr.so
</includes>
<outputDirectory>${project.build.outputDirectory}/questdb/lib</outputDirectory>
</artifactItem>
</artifactItems>
<overWriteReleases>true</overWriteReleases>
</configuration>
</execution>
</executions>
</plugin>

Note: Adjust the dependency coordinates (org.questdb:questdb:9.0.3) to match the exact QuestDB verson in your project.

Then package those files into your image or distribution and run with -Dquestdb.libs.dir=${app.home}/questdb/lib.

C) Gradle (Kotlin DSL)

dependencies {
implementation("org.questdb:questdb:9.0.3")
}
val copyQuestDbNatives by tasks.registering(Copy::class) {
val qdbJar = configurations.runtimeClasspath.get().files
.first { it.name.startsWith("questdb-") && it.extension == "jar" }
from(zipTree(qdbJar)) {
include("io/questdb/bin/linux-x86-64/libquestdb.so")
include("io/questdb/bin/linux-x86-64/libquestdbr.so")
}
into(layout.buildDirectory.dir("questdb/lib"))
}
tasks.named("processResources") { dependsOn(copyQuestDbNatives) }

Note: Adjust the dependency coordinates (org.questdb:questdb:9.0.3) to match the exact QuestDB verson in your project.

Bundle ${buildDir}/questdb/lib with your app and set -Dquestdb.libs.dir accordingly.


Alternative: change Java’s temp dir

If you can’t rebuild images right now, redirect the extraction location away from /tmp to a mount that allows executable mappings:

# Example: a tmpfs with exec permitted, sized small
sudo mkdir -p /run/questdb-tmp
sudo mount -t tmpfs -o size=32m,mode=0755 tmpfs /run/questdb-tmp
java -Djava.io.tmpdir=/run/questdb-tmp -jar your-app.jar

This avoids /tmp, but it’s less ideal than pre-bundling because you’re still extracting at runtime.


Troubleshooting

UnsatisfiedLinkError: cannot map file, Operation not permitted

  • The target directory is on a noexec mount. Move the libs to a different mount and update -Dquestdb.libs.dir.

It still writes to /tmp

  • Ensure -Dquestdb.libs.dir is present before any QuestDB classes initialize.
  • Check typos: the property name must be exactly questdb.libs.dir.

Wrong architecture

  • Extract from linux-aarch64 on ARM64, not linux-x86-64.

System Compatibility

Minimal distros (e.g., distroless/alpine)

  • QuestDB requires glibc 2.17+ on x86_64 and 2.28+ on ARM64. Make sure your base image meets this. QuestDB currently does not support musl/Alpine. Please vote for this issue if you need it.

Official images & distributions: already handled (no action required)

QuestDB’s official container images and platform-specific distributions already include the native libs (e.g., under /app/lib) and start the JVM with:

-Dquestdb.libs.dir=/app/lib

If you're using these, you do not need to change anything — no runtime extraction to /tmp occurs.


Closing notes

  • Prefer immutable builds with -Dquestdb.libs.dir.
  • Use -Djava.io.tmpdir only as a stopgap.
  • Keeping /tmp noexec is good practice—just don’t rely on it for native libs.
Subscribe to our newsletters for the latest. Secure and never shared or sold.