Why Docker for Oracle 23ai?
Running Oracle 23ai in Docker is the fastest way to get a fully featured database instance running on any machine — including macOS (Apple Silicon), Windows, and Linux — without a manual install. You get a consistent, reproducible environment that you can spin up in under two minutes, tear down cleanly, and version-control alongside your application code.
There are two official image sources:
| Source | Image | Notes |
|---|---|---|
| Oracle Container Registry | container-registry.oracle.com/database/free:latest |
Official Oracle image, requires free Oracle account login |
| Gerald Venzl (community) | gvenzl/oracle-free:23-slim |
No login required, widely used, multiple size variants |
This guide uses the gvenzl/oracle-free images — they require no registry login, start faster, and are the most widely adopted approach in the community.
Choosing the Right Image
The gvenzl/oracle-free repository offers four variants:
| Image | Size | Startup time | Best for |
|---|---|---|---|
gvenzl/oracle-free:23-slim |
~1.5 GB | ~90 seconds | Development, CI/CD pipelines |
gvenzl/oracle-free:23 |
~3 GB | ~90 seconds | Standard tools included |
gvenzl/oracle-free:23-full |
~5 GB | ~90 seconds | All Oracle components |
gvenzl/oracle-free:23-slim-faststart |
~4.6 GB | ~10 seconds | Local dev where fast restart matters |
The faststart variants contain a pre-built database — they are larger to pull but start almost instantly after the first run. For CI/CD pipelines where cold start time matters, faststart is worth the larger image size.
Note: If you need Oracle Multilingual Engine (MLE) for JavaScript stored procedures, use the non-slim variants — MLE is not included in the slim images.
Quick Start
Pull and run the slim image:
docker pull gvenzl/oracle-free:23-slim
docker run -d \
--name oracle23ai \
-p 1521:1521 \
-e ORACLE_PASSWORD=StrongPass1# \
gvenzl/oracle-free:23-slim
Monitor the startup:
docker logs -f oracle23ai
Wait for this line before attempting to connect:
DATABASE IS READY TO USE!
Connect immediately:
docker exec -it oracle23ai sqlplus / as sysdba
Production-Ready Setup with Docker Compose
For anything beyond a quick test, use Docker Compose. It captures all your configuration in a single file, makes startup and teardown one command, and integrates cleanly into a multi-service application stack.
Create a docker-compose.yml in your project root:
version: "3.8"
services:
oracle:
image: gvenzl/oracle-free:23-slim
container_name: oracle23ai
restart: unless-stopped
ports:
- "1521:1521"
environment:
ORACLE_PASSWORD: ${ORACLE_PASSWORD:-StrongPass1#}
ORACLE_DATABASE: APPDB
volumes:
- oracle-data:/opt/oracle/oradata
- ./init-scripts:/container-entrypoint-initdb.d
healthcheck:
test: ["CMD", "sqlplus", "-L", "system/${ORACLE_PASSWORD:-StrongPass1#}@//localhost:1521/FREEPDB1", "@/dev/null"]
interval: 30s
timeout: 10s
retries: 10
start_period: 120s
shm_size: 1g
mem_limit: 3g
cpus: "2"
volumes:
oracle-data:
driver: local
Start the stack:
docker compose up -d
docker compose logs -f oracle
Stop and clean up (data is preserved in the volume):
docker compose down
Destroy everything including data:
docker compose down -v
Persistent Storage
Without a volume, all your data is lost when the container stops. Always mount a named volume or a host directory to /opt/oracle/oradata:
# Named volume (recommended — Docker manages the location)
docker volume create oracle-data
docker run -d \
--name oracle23ai \
-p 1521:1521 \
-e ORACLE_PASSWORD=StrongPass1# \
-v oracle-data:/opt/oracle/oradata \
gvenzl/oracle-free:23-slim
# Host directory mount (useful when you need direct file access)
mkdir -p ~/oracle-data
docker run -d \
--name oracle23ai \
-p 1521:1521 \
-e ORACLE_PASSWORD=StrongPass1# \
-v ~/oracle-data:/opt/oracle/oradata \
gvenzl/oracle-free:23-slim
When using a host directory on Linux, the directory must be writable by uid
54321(the oracle user inside the container):sudo chown -R 54321:54321 ~/oracle-data
Automatic Database and User Initialization
The gvenzl/oracle-free images support initialization scripts. Any .sql or .sh file placed in /container-entrypoint-initdb.d inside the container runs automatically on first startup — after the database is created but before it is marked ready.
Create an init-scripts/ folder in your project and add your setup SQL:
mkdir init-scripts
init-scripts/01_create_user.sql:
-- Connect to FREEPDB1 and create application user
ALTER SESSION SET CONTAINER = FREEPDB1;
CREATE USER appuser IDENTIFIED BY AppPass1#;
GRANT DB_DEVELOPER_ROLE TO appuser;
GRANT UNLIMITED TABLESPACE TO appuser;
GRANT CREATE SESSION TO appuser;
init-scripts/02_create_schema.sql:
ALTER SESSION SET CONTAINER = FREEPDB1;
-- Create tables
CREATE TABLE appuser.products (
id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
name VARCHAR2(200) NOT NULL,
description CLOB,
embedding VECTOR(1536),
created_at TIMESTAMP DEFAULT SYSTIMESTAMP
);
-- Create vector index for semantic search
CREATE VECTOR INDEX products_vec_idx ON appuser.products (embedding)
ORGANIZATION INMEMORY NEIGHBOR GRAPH
DISTANCE COSINE
WITH TARGET ACCURACY 95;
Mount the folder in your docker run command:
docker run -d \
--name oracle23ai \
-p 1521:1521 \
-e ORACLE_PASSWORD=StrongPass1# \
-v oracle-data:/opt/oracle/oradata \
-v $(pwd)/init-scripts:/container-entrypoint-initdb.d \
gvenzl/oracle-free:23-slim
Scripts run in alphabetical order — prefix them with 01_, 02_ etc. to control execution order. Scripts only run on first startup (when the data volume is empty).
Environment Variables Reference
| Variable | Required | Default | Description |
|---|---|---|---|
ORACLE_PASSWORD |
Yes | — | Password for SYS, SYSTEM, and PDBADMIN |
ORACLE_DATABASE |
No | — | Creates an additional PDB with this name |
ORACLE_RANDOM_PASSWORD |
No | — | Set to any value to generate a random password (printed in logs) |
APP_USER |
No | — | Creates an application user in FREEPDB1 |
APP_USER_PASSWORD |
No | — | Password for APP_USER |
Using APP_USER and APP_USER_PASSWORD is a convenient shortcut for simple setups:
docker run -d \
--name oracle23ai \
-p 1521:1521 \
-e ORACLE_PASSWORD=StrongPass1# \
-e APP_USER=appdev \
-e APP_USER_PASSWORD=AppPass1# \
gvenzl/oracle-free:23-slim
This automatically creates the appdev user in FREEPDB1 with DB_DEVELOPER_ROLE granted.
Connecting Your Application
Connection string formats
# SQL*Plus
sqlplus appdev/AppPass1#@localhost:1521/FREEPDB1
# JDBC (Java / Spring Boot)
jdbc:oracle:thin:@localhost:1521/FREEPDB1
# Python (python-oracledb)
oracle+oracledb://appdev:AppPass1#@localhost:1521/?service_name=FREEPDB1
# Node.js (node-oracledb)
"connectString": "localhost:1521/FREEPDB1"
# SQL Developer / DBeaver
Host: localhost Port: 1521 Service: FREEPDB1
Spring Boot with Docker Compose
Spring Boot 3.1+ has built-in Docker Compose support. Add the dependency and Spring auto-starts the compose stack when the app launches:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
</dependency>
# application.yml
spring:
datasource:
url: jdbc:oracle:thin:@localhost:1521/FREEPDB1
username: appdev
password: AppPass1#
driver-class-name: oracle.jdbc.OracleDriver
Python (python-oracledb thin mode — no Oracle Client needed)
import oracledb
conn = oracledb.connect(
user="appdev",
password="AppPass1#",
dsn="localhost:1521/FREEPDB1"
)
cursor = conn.cursor()
cursor.execute("SELECT banner FROM v$version")
print(cursor.fetchone())
conn.close()
Health Checks and Waiting for Ready
Never connect to Oracle before it signals it is ready. In shell scripts use a loop:
#!/bin/bash
echo "Waiting for Oracle to be ready..."
until docker exec oracle23ai sqlplus -L system/StrongPass1#@//localhost:1521/FREEPDB1 /dev/null 2>&1 | grep -q "Connected"; do
echo " Still starting..."
sleep 5
done
echo "Oracle is ready."
In CI/CD pipelines (GitHub Actions):
- name: Start Oracle
run: |
docker run -d \
--name oracle23ai \
-p 1521:1521 \
-e ORACLE_PASSWORD=StrongPass1# \
gvenzl/oracle-free:23-slim-faststart
- name: Wait for Oracle
run: |
timeout 120 bash -c 'until docker logs oracle23ai 2>&1 | grep -q "DATABASE IS READY TO USE"; do sleep 3; done'
- name: Run tests
run: npm test
Backup and Restore
Export with Data Pump
# Create a directory object inside the container
docker exec oracle23ai sqlplus system/StrongPass1#@//localhost:1521/FREEPDB1 <<EOF
CREATE OR REPLACE DIRECTORY dp_dir AS '/opt/oracle/backup';
GRANT READ, WRITE ON DIRECTORY dp_dir TO appdev;
EXIT;
EOF
# Run expdp
docker exec oracle23ai expdp appdev/AppPass1#@//localhost:1521/FREEPDB1 \
directory=dp_dir \
dumpfile=appdev_$(date +%Y%m%d).dmp \
logfile=appdev_export.log \
schemas=appdev
# Copy the dump file to your host
docker cp oracle23ai:/opt/oracle/backup/appdev_$(date +%Y%m%d).dmp ./backups/
Import with Data Pump
# Copy dump file into container
docker cp ./backups/appdev_20250301.dmp oracle23ai:/opt/oracle/backup/
# Run impdp
docker exec oracle23ai impdp appdev/AppPass1#@//localhost:1521/FREEPDB1 \
directory=dp_dir \
dumpfile=appdev_20250301.dmp \
logfile=appdev_import.log \
schemas=appdev \
remap_schema=appdev:appdev
Upgrading to a New Image Version
# Pull the new image
docker pull gvenzl/oracle-free:23-slim
# Stop and remove the old container (data volume is preserved)
docker compose down
# Start with the new image — data volume is reattached automatically
docker compose up -d
Because your data lives in a named volume, not in the container, upgrades are safe — the new container mounts the same volume and Oracle performs any required upgrades on first startup.
Troubleshooting
Container exits immediately
# Check logs for the actual error
docker logs oracle23ai
# Most common causes:
# 1. Not enough memory — increase Docker Desktop RAM to 4 GB minimum
# 2. Port 1521 already in use — check with: netstat -an | grep 1521
# 3. Volume permission issue on Linux — chown -R 54321:54321 /your/data/dir
ORA-12547 on fresh pull
This can happen when upgrading from Oracle 23c Free to 23ai Free if Docker has cached dangling image layers. Fix it by pruning stale images:
docker system prune -f
docker pull gvenzl/oracle-free:23-slim
Cannot connect — ORA-12541: no listener
# Check the listener inside the container
docker exec oracle23ai lsnrctl status
# Restart the listener
docker exec oracle23ai lsnrctl stop
docker exec oracle23ai lsnrctl start
Data volume appears empty after restart
You stopped the container with docker compose down -v which removes volumes. Use docker compose down (without -v) to preserve data.
Security Checklist for Team Environments
- Store
ORACLE_PASSWORDin a.envfile and add.envto.gitignore— never commit passwords - Use Docker secrets for production deployments on Docker Swarm or Kubernetes
- Expose port 1521 only to
127.0.0.1in shared environments:-p 127.0.0.1:1521:1521 - Rotate the SYS/SYSTEM password immediately after first startup in any environment accessible to others
- Use a dedicated application user (
APP_USER) with minimal privileges — never connect your app as SYSTEM
# Bind to localhost only — not accessible from other machines on the network
docker run -d \
--name oracle23ai \
-p 127.0.0.1:1521:1521 \
-e ORACLE_PASSWORD=StrongPass1# \
gvenzl/oracle-free:23-slim