Exasol Core SQL engine, single-node for dev, testing, edge & embedded analytics.
6.4K
This Docker Hub guide shows the shortest path to a local Exasol Nano container and then lists optional mounts for SLCs, JDBC drivers, and BucketFS content.
All Nano examples use docker.io/exasol/nano:latest and include the recommended runtime limits:
--shm-size=512mb--pids-limit=-1Unless noted otherwise, commands use podman. Docker users can usually replace podman with docker; SLC/UDF examples list Docker-specific security options separately.
For reproducible automation, prefer a pinned published image tag instead of latest.
/exa volume.Database is now up and running!.127.0.0.1:8563 with user sys and the initial SYS password for that runtime.podman run --rm -it --name exanano \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
docker.io/exasol/nano:latest
Stop an interactive run with Ctrl+C / Ctrl+D.
/exa containsMount /exa to keep database state between container runs. Persisted data includes:
/exa/exasol.conf/exa/logs/exa/storage/exa/slc/exa/jdbc/exa/bucketfs/exa/bucketfs.confpodman run --rm -it \
--shm-size=512mb \
--pids-limit=-1 \
-v exanano-data:/exa \
docker.io/exasol/nano:latest init help
mkdir -p "$PWD/exa"
podman run --rm -it --name exanano \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v "$PWD/exa:/exa" \
docker.io/exasol/nano:latest
Use an absolute host path and make sure it is writable by your rootless user.
podman run -d --name exanano \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
docker.io/exasol/nano:latest
Stop and remove the detached container:
podman stop -t 60 exanano && podman rm exanano
Pass DB parameters with init params='k=v ...'. On an empty /exa, Nano bootstraps the runtime, applies the parameters, and starts the DB. The values are persisted in /exa/exasol.conf, so later restarts can usually omit the extra init params=... arguments.
podman run --rm -it --name exanano \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
docker.io/exasol/nano:latest init params='dbram=4096 nrOfCores=8'
For CI/CD or secret-mount workflows, provide the initial SYS password from a file during the first deployment only:
podman run --rm -it --name exanano \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
-v "$PWD/sys_password:/run/secrets/sys_password:ro" \
docker.io/exasol/nano:latest init sys_password_file=/run/secrets/sys_password
For an interactive first deployment, use:
podman run --rm -it --name exanano \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
docker.io/exasol/nano:latest init sys_password_prompt
Notes:
/exa runtimeALTER USERexasolNano generates a self-signed TLS certificate by default, and the SQL port accepts TLS connections only. When startup is finished, the container logs print:
Database is now up and running!exaplus example command that includes the SQL port and certificate fingerprintEnter the SYS password when prompted.Watch startup logs with:
podman logs -f exanano
Freshly initialized Nano runtimes use:
sysIf you do not provide an explicit initial SYS password during first deployment, the default is exasol.
For local quick starts, keep the SQL port on localhost and do not expose this login to untrusted networks. To bind the SQL port to localhost only, replace -p 8563:8563 in the run commands with -p 127.0.0.1:8563:8563.
Install pyexasol in a virtual environment:
python3 -m venv .venv
. .venv/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install pyexasol
Then run a first query. This local example disables certificate verification for convenience; do not use that setting for remote or untrusted networks. If your client supports certificate fingerprints, prefer the fingerprint shown in the Nano startup logs.
NANO_SYS_PASSWORD=${NANO_SYS_PASSWORD:-exasol} python3 - <<'PY'
import os
import ssl
import pyexasol
conn = pyexasol.connect(
dsn="127.0.0.1:8563",
user="sys",
password=os.environ["NANO_SYS_PASSWORD"],
encryption=True,
websocket_sslopt={"cert_reqs": ssl.CERT_NONE},
)
print(conn.execute("SELECT 1").fetchall())
conn.execute("CREATE SCHEMA IF NOT EXISTS DEMO")
conn.execute("OPEN SCHEMA DEMO")
conn.execute("CREATE OR REPLACE TABLE T (ID INT)")
conn.execute("INSERT INTO T VALUES (1), (2)")
print(conn.execute("SELECT * FROM T ORDER BY ID").fetchall())
conn.close()
PY
Connection basics:
127.0.0.18563sysjdbc:exa:localhost:8563If your SQL client supports certificate fingerprints, use the fingerprint from the Nano startup logs. Otherwise configure the client to trust the generated self-signed certificate, or use the client-specific no-cert-check option only for local testing.
Official Exasol documentation:
https://docs.exasol.com/db/latest/https://docs.exasol.com/db/latest/sql_reference.htmNano is SLC-only. To install packaged language containers (java, python, r), run init slc install=all once for the /exa runtime.
For packaged SLC/UDF execution with Podman, add --security-opt unmask=ALL:
podman run --rm -it --name exanano \
--security-opt unmask=ALL \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
docker.io/exasol/nano:latest init slc install=all
For packaged SLC/UDF execution with rootless Docker, add --security-opt seccomp=unconfined and --security-opt systempaths=unconfined:
docker run --rm -it --name exanano \
--security-opt seccomp=unconfined \
--security-opt systempaths=unconfined \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
docker.io/exasol/nano:latest init slc install=all
On some rootful Docker hosts with AppArmor enabled, UDF execution may additionally require --security-opt apparmor=unconfined.
Useful SLC arguments:
init slc install=java,pythoninit slc install=all version=11.1.1reinstall=1For later runs that use SLC UDFs, reuse the same runtime-specific security and resource options.
Mount /exa/slc separately if you want to reuse or inspect installed SLC files independently from the rest of the Nano runtime data:
mkdir -p "$PWD/exa" "$PWD/slc"
podman run --rm -it --name exanano \
--security-opt unmask=ALL \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v "$PWD/exa:/exa" \
-v "$PWD/slc:/exa/slc" \
docker.io/exasol/nano:latest init slc install=all
If you already have a prepared SLC root from a previous run or another host, mount it the same way at /exa/slc.
JDBC drivers live below /exa/jdbc/<DRIVERNAME>/. A driver directory usually contains settings.cfg and one or more JDBC jar files.
Example host directory:
$PWD/SQLITE/
├── settings.cfg
└── sqlite-jdbc-3.46.1.3.jar
Example settings.cfg:
DRIVERNAME=SQLITE
JAR=sqlite-jdbc-3.46.1.3.jar
DRIVERMAIN=org.sqlite.JDBC
FETCHSIZE=100000
INSERTSIZE=-1
PREFIX=jdbc:sqlite:
NOSECURITY=YES
Mount it read-only:
podman run --rm -it --name exanano \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
-v "$PWD/SQLITE:/exa/jdbc/SQLITE:ro" \
docker.io/exasol/nano:latest
If the JDBC driver is used from SLC/UDF code, combine this mount with the SLC runtime options from the previous section.
Nano reconciles user BucketFS entries from /exa/bucketfs/<bucketfs>/<bucket>/... and exposes them to UDFs at /buckets/<bucketfs>/<bucket>/....
Create content directly below the host runtime directory:
mkdir -p "$PWD/exa/bucketfs/testing/publicbucket"
printf 'hello from public\n' > "$PWD/exa/bucketfs/testing/publicbucket/README.md"
Nano reconciles /exa/bucketfs.conf automatically. Newly discovered buckets start as public buckets.
Layer a second bind mount below /exa:
podman run --rm -it --name exanano \
--shm-size=512mb \
--pids-limit=-1 \
-p 8563:8563 \
-v exanano-data:/exa \
-v "$PWD/bucketfs:/exa/bucketfs" \
docker.io/exasol/nano:latest
Then create host content under the mounted directory:
mkdir -p "$PWD/bucketfs/testing/publicbucket"
printf 'hello from public\n' > "$PWD/bucketfs/testing/publicbucket/README.md"
Reconcile notes:
/exa/bucketfs.conf automatically/buckets/... as a supported featureThe copy/paste path for private buckets is to use a host bind mount for /exa, for example -v "$PWD/exa:/exa". This gives you direct access to both the bucket content and the generated bucketfs.conf file.
Create the bucket content:
mkdir -p "$PWD/exa/bucketfs/testing/secure"
printf 'secret data\n' > "$PWD/exa/bucketfs/testing/secure/README.md"
Wait until Nano discovers the bucket and writes the config entry:
while ! grep -q ' testing secure ' "$PWD/exa/bucketfs.conf" 2>/dev/null; do
sleep 1
done
Convert exactly that discovered bucket to private. This example uses password test1 and base64-encodes it automatically:
nano_bucketfs_make_private() {
local bucketfs_cfg="$1"
local runtime_root="$2"
local bucketfs_name="$3"
local bucket_name="$4"
local password="$5"
python3 - "$bucketfs_cfg" "$runtime_root" "$bucketfs_name" "$bucket_name" "$password" <<'PY'
import base64
import os
import sys
from pathlib import Path
cfg_path = Path(sys.argv[1])
runtime_root = sys.argv[2].rstrip('/')
fs_name = sys.argv[3]
bucket_name = sys.argv[4]
password_b64 = base64.b64encode(sys.argv[5].encode('utf-8')).decode('ascii')
matches = 0
out_lines = []
for line in cfg_path.read_text(encoding='utf-8').splitlines():
stripped = line.strip()
if not stripped or stripped.startswith('#'):
out_lines.append(line)
continue
tokens = stripped.split()
if len(tokens) == 6 and tokens[1] == fs_name and tokens[2] == bucket_name:
matches += 1
out_lines.append(
f"{runtime_root}/bucketfs/{fs_name}/{bucket_name} "
f"{fs_name} {bucket_name} /buckets/{fs_name}/{bucket_name} {password_b64} -"
)
else:
out_lines.append(line)
if matches != 1:
raise SystemExit(
f"Expected exactly one entry for {fs_name}/{bucket_name} in {cfg_path}, found {matches}"
)
tmp_path = cfg_path.with_name(cfg_path.name + '.tmp')
tmp_path.write_text(''.join(line + '\n' for line in out_lines), encoding='utf-8')
os.replace(tmp_path, cfg_path)
print(f"Updated {cfg_path} for private bucket {fs_name}/{bucket_name}")
PY
}
nano_bucketfs_make_private \
"$PWD/exa/bucketfs.conf" \
/exa \
testing secure test1
Important: the config line must use the container/runtime path /exa/bucketfs/testing/secure, not the host path $PWD/exa/bucketfs/testing/secure.
Create the matching SQL connection:
CREATE CONNECTION bfs_secure
TO 'bucketfs:testing/secure'
IDENTIFIED BY 'test1';
The following compose.yaml is the compact starting point for Docker Compose or podman-compose. It starts Nano in the foreground with persisted data and the SQL port exposed.
services:
exanano:
image: docker.io/exasol/nano:latest
container_name: exanano
ports:
- "8563:8563"
volumes:
- exanano-data:/exa
shm_size: "512mb"
pids_limit: -1
stdin_open: true
tty: true
volumes:
exanano-data:
Start:
docker compose up
or:
podman-compose up
Stop:
docker compose down
or:
podman-compose down
Useful compose additions:
# Use host directories instead of the named /exa volume.
volumes:
- ./exa:/exa
- ./SQLITE:/exa/jdbc/SQLITE:ro
- ./bucketfs:/exa/bucketfs
# Podman SLC/UDF execution.
security_opt:
- unmask=ALL
command: ["init", "slc", "install=all"]
# Docker rootless SLC/UDF execution.
security_opt:
- seccomp=unconfined
- systempaths=unconfined
command: ["init", "slc", "install=all"]
On rootful Docker hosts with AppArmor enabled, also add apparmor=unconfined to security_opt.
Nano stores persistent logs below /exa/logs.
/exaCreate a compressed archive directly from the host directory:
tar -C "$PWD/exa" -czf exanano-logs.tgz logs
/exaCreate a temporary container, mount the Nano volume read-only, and write the archive to the current directory:
podman run --rm \
--shm-size=512mb \
--pids-limit=-1 \
-v exanano-data:/exa:ro \
-v "$PWD:/out" \
docker.io/library/busybox:latest \
sh -lc 'tar -C /exa -czf /out/exanano-logs.tgz logs'
If you use Docker, the same command works with docker run instead of podman run.
If the container is still present, also capture the container stdout/stderr stream:
podman logs exanano > exanano-container.log 2>&1
If you use Docker, replace podman logs with docker logs.
/dev/shm is too small:
--shm-size=512mb for normal runs60 MiB available under /dev/shm/exa--security-opt apparmor=unconfined/exa/jdbc/<DRIVERNAME> and include settings.cfg/exa/bucketfs/<bucketfs>/<bucket>/exa/bucketfs.conf/exa/..., not the host path.run packagePublished .run packages are stored in Docker Hub as ORAS artifacts with immutable tags matching the file name:
oras pull docker.io/exasol/nano:exasol-nano-db-<version>-<x86_64|aarch64>.run
chmod +x ./exasol-nano-db-<version>-<x86_64|aarch64>.run
./exasol-nano-db-<version>-<x86_64|aarch64>.run -- help
Without a local ORAS install, use the ORAS container image from the current directory:
podman run --rm \
-v "$PWD:$PWD" -w "$PWD" \
ghcr.io/oras-project/oras:v1.2.3 \
pull docker.io/exasol/nano:exasol-nano-db-<version>-<x86_64|aarch64>.run
The pulled file lands in the current directory, same as with a local oras pull.
Public pulls do not require login. Pushes and private pulls require authentication. curl can download OCI blobs through the registry API, but ORAS is the supported path.
Content type
Image
Digest
sha256:459934930…
Size
136.3 MB
Last updated
about 1 month ago
docker pull exasol/nano