Oracle deployments have a reputation. They're slow to set up, painful to automate, and the tooling ecosystem tends to assume you have deep pockets. If you've tried to build a reliable Oracle deployment pipeline, you've probably hit at least one of these walls:
- Flyway's Oracle support is locked behind Flyway Teams
- Liquibase's Oracle features are split across free and commercial tiers
- Rolling your own with SQLPlus scripts works — until it doesn't, and there's no audit trail when something goes wrong
- Connection strings with credentials scattered across environment files and CI/CD config
drm-cli doesn't replace Flyway. It layers on top of it — adding release tracking, encrypted credentials, retry logic, and pre/post deployment scripting. And it's free and open-source, which means Oracle-grade deployment management without an Oracle-grade price tag.
This tutorial walks through a complete Oracle deployment using drm-cli + Flyway.
Prerequisites
Before you start, you'll need:
- drm-cli installed — installation guide here
- Flyway CLI installed and on your PATH — download here
-
Oracle JDBC driver (
ojdbc11.jar) — available from Oracle's driver download page - An Oracle database you can connect to (Oracle XE works fine for testing)
- Python 3.10+
Project Structure
drm-cli works with a straightforward directory structure. Here's what a minimal Oracle project looks like:
my-oracle-project/
├── drm.yml
├── migrations/
│ ├── V1__create_accounts_table.sql
│ ├── V2__add_account_status.sql
│ └── V3__create_transactions_table.sql
└── drivers/
└── ojdbc11.jar
Your migrations follow standard Flyway naming conventions — drm-cli doesn't change that. It just takes responsibility for the release lifecycle around them.
Step 1 — Configure drm.yml
drm.yml is the project configuration file drm-cli reads at runtime. Here's a working example for an Oracle deployment:
project: my-oracle-project
version: "1.0.0"
release:
history_db: release_history.db # SQLite file — stores your full release audit trail
retry_attempts: 3
retry_delay_seconds: 10
targets:
- name: oracle-prod
tool: flyway
database_type: oracle
migrations_path: ./migrations
drivers_path: ./drivers
connection:
encrypted: false
url: "jdbc:oracle:thin:@//localhost:1521/XEPDB1"
user: "deploy_user"
password: "your-password-here"
A note on the connection block: encrypted: false here for clarity, but drm-cli supports encrypting credentials at rest using a key you manage. We'll cover that in a follow-up article — for now, plain credentials are fine for a local test environment.
Step 2 — Write Your Migrations
Standard Flyway SQL migrations. Nothing unusual here:
-- V1__create_accounts_table.sql
CREATE TABLE accounts (
account_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
account_name VARCHAR2(100) NOT NULL,
created_at TIMESTAMP DEFAULT SYSTIMESTAMP
);
-- V2__add_account_status.sql
ALTER TABLE accounts
ADD status VARCHAR2(20) DEFAULT 'ACTIVE';
-- V3__create_transactions_table.sql
CREATE TABLE transactions (
transaction_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
account_id NUMBER NOT NULL,
amount NUMBER(15,2) NOT NULL,
posted_at TIMESTAMP DEFAULT SYSTIMESTAMP,
CONSTRAINT fk_account FOREIGN KEY (account_id) REFERENCES accounts(account_id)
);
Step 3 — Run Your First Deployment
With your config in place, deploy with:
drm deploy --target oracle-prod
You'll see output like this:
[drm-cli] Starting deployment → oracle-prod
[drm-cli] Tool: flyway | Database: oracle
[drm-cli] Running Flyway migrate...
[drm-cli] Flyway: Successfully applied 3 migrations to schema "DEPLOY_USER"
[drm-cli] Recording release to history...
[drm-cli] Deployment complete — Release ID: 001 | Status: SUCCESS
That last line is what makes drm-cli different from running Flyway directly. Every deployment is written to the local release history database — timestamp, target, release ID, outcome.
Step 4 — Check Release History
drm history --target oracle-prod
Release History — oracle-prod
─────────────────────────────────────────────────────────────────
ID Version Status Started Completed
001 1.0.0 SUCCESS 2026-04-20 09:14:32 2026-04-20 09:14:38
─────────────────────────────────────────────────────────────────
1 release found.
This audit trail lives in the local SQLite file (release_history.db). It's yours — no cloud service, no external dependency.
Step 5 — What Happens When a Migration Fails
This is where retry logic matters. Let's say your network connection to Oracle drops mid-deployment — a perfectly ordinary thing to happen in production.
Configure retry_attempts and retry_delay_seconds in drm.yml (already set above), and drm-cli will automatically retry the deployment up to 3 times before marking it as failed.
[drm-cli] Starting deployment → oracle-prod
[drm-cli] Tool: flyway | Database: oracle
[drm-cli] Running Flyway migrate...
[drm-cli] Connection error — retrying in 10 seconds (attempt 1/3)
[drm-cli] Connection error — retrying in 10 seconds (attempt 2/3)
[drm-cli] Flyway: Successfully applied 1 migration to schema "DEPLOY_USER"
[drm-cli] Recording release to history...
[drm-cli] Deployment complete — Release ID: 002 | Status: SUCCESS
If all retries are exhausted, the failed attempt is still recorded in release history — so you know exactly what ran, what didn't, and when.
Encrypting Your Oracle Credentials
Storing plaintext database credentials is a habit worth breaking. drm-cli supports encrypting the connection block in drm.yml using a key you control.
Switch to encrypted mode:
connection:
encrypted: true
url: "gAAAAABk..." # encrypted value
user: "gAAAAABk..." # encrypted value
password: "gAAAAABk..." # encrypted value
Encrypt your values with:
drm encrypt --value "jdbc:oracle:thin:@//localhost:1521/XEPDB1"
drm-cli prompts for your encryption key and returns the encrypted string. The key never touches drm.yml — you pass it at runtime or load it from an environment variable.
Full encryption setup is covered in the drm-cli docs.
Using Liquibase Instead of Flyway
If your team uses Liquibase rather than Flyway, swap tool: flyway for tool: liquibase in drm.yml and point migrations_path at your changelog directory. The rest of the workflow is identical.
yaml
targets:
- name: oracle-prod
tool: liquibase
database_type: oracle
changelog_file: ./changelogs/db.changelog-root.xml
drivers_path: ./drivers
connection:
encrypted: false
url: "jdbc:oracle:thin:
Top comments (0)