DevOps

CI/CD for APEX with GitHub Actions

Put your APEXlang app under source control and ship it on every push with an automated pipeline.

Advanced⏱ 3 min readUpdated: 2026-10-10

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

APEXlang export and import need APEX 26.1 or later on both the source and the target instance — keep both on the same APEX version. You also need a SQLcl release with the APEXlang 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 as db.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.log
  • actions/setup-java installs 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, use distribution: temurin.
  • sqlcl-latest.zip always fetches the newest SQLcl, and sql -V prints the version in the log. For fully repeatable builds, download a specific release zip instead.
  • apex validate is 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 validate or apex import reports 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. -S only hides the SQLcl banner and prompts.

⚠ Secrets, not commits

Database credentials go in GitHub Actions secrets — never commit them. Deploy with a least-privilege user: an apex import connected as the workspace's parsing schema works and needs no DBA rights.

💡 Shorter setup

The community action 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/3

What makes APEX CI/CD possible?

Where do DB credentials belong?

Why does the workflow grep the SQLcl log?

Need this delivered?

Request a quote