CI/CD for APEX with GitHub Actions
Put your APEXlang app under source control and ship it on every push with an automated pipeline.
Once your APEX app lives in Git as APEXlang files, a pipeline can deploy it for you. This guide builds a GitHub Actions workflow that installs SQLcl, checks that the APEXlang source compiles, and imports the app into the target database on every push to main.
ℹ What you need
apex commands (we tested 26.1.2; the current download is 26.3.0). Current SQLcl releases run on Java 17, 21 or 25.Export the app as APEXlang files
In SQLcl, connect as the workspace's parsing schema and export the app in APEXlang format:
apex export -applicationid 100 -exptype APEXLANG -skipexportdate -dir apex -force
SQLcl creates apex/<app-alias>/ (the alias in lowercase) with application.apx, one .apx file per page under pages/, shared-components/, and deployments/default.json, which holds the application ID used on import. -skipexportdate keeps repeated exports byte-identical, so a diff shows only real changes; -force clears the folder first, so deleted pages don't linger. Commit the folder:
git add apex/ git commit -m "feat: add status filter to orders page" git push
Store the credentials as secrets
In your repository go to Settings → Environments, create an environment named production, and add three environment secrets. Plan limits apply: on GitHub Free, environment secrets work only in public repositories, and required reviewers on private repositories need GitHub Enterprise. On a private repository under GitHub Free, add the same three names as repository secrets (Settings → Secrets and variables → Actions) and delete the environment: production line.
DB_USER— the deploy user (for example the workspace's parsing schema).DB_PASSWORD— its password.DB_CONNECT— an EZConnect string such asdb.example.com:1521/FREEPDB1.
The deploy workflow
Save this as .github/workflows/deploy-apex.yml and set APP_DIR to the folder SQLcl created:
name: deploy-apex
on:
push:
branches: [main]
paths: ["apex/**"]
workflow_dispatch:
concurrency:
group: deploy-apex
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
env:
APP_DIR: apex/my-app
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: oracle
java-version: "25"
- name: Install SQLcl
run: |
curl -sSL -o sqlcl.zip https://download.oracle.com/otn_software/java/sqldeveloper/sqlcl-latest.zip
unzip -q sqlcl.zip
./sqlcl/bin/sql -V
- name: Validate APEXlang
run: |
./sqlcl/bin/sql -S /nolog <<EOF | tee validate.log
apex validate -input $APP_DIR
exit
EOF
grep -q "Validation successful." validate.log
- name: Import app
env:
DB_USER: ${{ secrets.DB_USER }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
DB_CONNECT: ${{ secrets.DB_CONNECT }}
run: |
./sqlcl/bin/sql -S /nolog <<EOF | tee import.log
connect $DB_USER/"$DB_PASSWORD"@$DB_CONNECT
apex import -input $APP_DIR
exit
EOF
grep -q "Import successful." import.logactions/setup-javainstalls Oracle JDK 25, which Oracle offers under its free-use licence. Oracle JDK 21 updates from the October 2026 CPU (20 October 2026) onward are planned to move to the Java SE OTN licence. If you prefer Java 21, usedistribution: temurin.sqlcl-latest.zipalways fetches the newest SQLcl, andsql -Vprints the version in the log. For fully repeatable builds, download a specific release zip instead.apex validateis an offline check that the APEXlang compiles, so that step needs no connection and no secrets. It does not check your credentials or your database objects: a wrong password fails the import step, but a missing table or column is not caught by either step. It only appears when the page runs, so create schema objects before the import (for example with a migration step) and smoke-test the app after deploying.- SQLcl exits with code 0 even when
apex validateorapex importreports errors, so each step checks the log for the success line and fails the job if it is missing. - The heredoc passes the password on standard input, so it never appears on the command line, and SQLcl does not echo that input. GitHub also masks secret values in job logs.
-Sonly hides the SQLcl banner and prompts.
⚠ Secrets, not commits
apex import connected as the workspace's parsing schema works and needs no DBA rights.💡 Shorter setup
gvenzl/setup-oracle-sqlcl@v1 replaces the Java and SQLcl steps with one uses: line and puts sql on the PATH. It is not an official Oracle product, so review it before you adopt it.Check your understanding
Check your understanding
0% · 0/3What makes APEX CI/CD possible?
Where do DB credentials belong?
Why does the workflow grep the SQLcl log?
Need this delivered?
Request a quote