• Task
  • Version · 6.0
  • Create

Deploy to multiple Oracle schemas using a proxy user

Last updated: September 29, 2026

Oracle proxy users let Liquibase deploy to many schemas without granting the elevated CREATE ANY privileges that multi-schema deployments otherwise require. Each schema keeps its own owner, one shared proxy user connects through each of them in turn, and a single tracking schema holds the Liquibase tracking tables for every schema.

Because every connection authenticates as that one proxy user, your pipeline stores and rotates a single secret rather than one password per schema.

Before you begin

  • An Oracle database you can connect to, with DBA access to create users, roles, and grants

  • Liquibase Secure installed on the machine or CI runner that runs the deployment

  • The Oracle JDBC driver available to Liquibase, such as ojdbc11.jar

  • The reference project this guide is based on: cs_oracle_proxy

Procedure

1

Create the proxy user

The proxy user is the only account your pipeline authenticates as. It needs nothing beyond the ability to open a session, because every privilege it uses comes from the schema it connects through.

Be sure to:

  • Replace liquibase_proxy with the proxy user name you want. For example, LIQUIBASE_PROXY

  • Replace liquibase_proxy_pw with the password for that user

  • Replace users with your tablespace name

loading
2

Create the tracking schema and the managed schemas

Create one schema per application schema you deploy to, plus one dedicated schema that holds the tracking tables. Keeping the tracking tables in their own schema means every managed schema reports into one deployment history rather than each keeping a separate copy.

Be sure to:

  • Replace SCHEMA_A and SCHEMA_B with your application schema names

  • Replace LIQUIBASE with the name you want for the tracking schema

loading
3

Create the deployment role and grant it to each managed schema

Collect the privileges Liquibase needs to deploy objects into a role, then grant that role to every schema owner. Using a role means you add a new schema later by granting one role instead of repeating each privilege.

Be sure to:

  • Replace LIQUIBASE_ROLE with the role name you want

  • Add a GRANT LIQUIBASE_ROLE TO line for every schema you manage

loading
4

Create the tracking role and grant it to the tracking schema

The tracking schema needs far less than the managed schemas. It only creates and maintains the tracking tables, so give it its own narrower role.

loading
5

Allow the proxy user to connect through each schema

Grant proxy connect for every managed schema and for the tracking schema. Without this grant the proxy user can authenticate but cannot assume the schema, and the connection fails.

loading
6

Create the tracking tables

Point url at the tracking schema through the proxy user, then run any command that reads the tracking tables. liquibase status is enough, because it reads them without deploying anything. Liquibase creates both tables on that first connection.

Be sure to:

  • Replace your-host:1521/LB_TEST with your own host, port, and service name

  • Set liquibase.command.password in the same properties file, as shown in Step 8

# in liquibase.properties
url: jdbc:oracle:thin:liquibase_proxy[LIQUIBASE]/@your-host:1521/LB_TEST

# then run
liquibase status
7

Grant access to the tracking tables

Every managed schema writes to the tracking tables in the tracking schema, so each one needs DML on all three tables.

Be sure to:

  • Repeat the three grants for every schema you manage

loading

Optional: Add public synonyms

Public synonyms are a convenience on top of these grants, not a substitute for them. A synonym only creates an alias so that DATABASECHANGELOG resolves without a schema prefix, and it carries no privileges of its own, so the grants above are required either way.

Liquibase does not need synonyms, because liquibaseSchemaName already tells it where the tracking tables live. Add them only if your DBAs or your own scripts query the tracking tables without qualifying the schema.

Be sure to:

  • Run these as a user with the CREATE PUBLIC SYNONYM privilege, such as SYSDBA

loading
8

Configure your Liquibase properties

Set liquibaseSchemaName to the tracking schema so Liquibase looks for the tracking tables there rather than in the schema it is deploying to. Set defaultSchemaName to the schema you want to deploy to.

Note: The defaultSchemaName and username values below apply when you run a single schema by hand. The deployment loop in Step 10 overrides both per schema, so the values here act as a local default rather than the setting your pipeline uses.

Be sure to:

  • Replace the url host, port, and service name with your own

  • Replace LIQUIBASE with your tracking schema name

  • Replace SCHEMA_A with the schema you want to deploy to by default

loading
9

Organize your changelogs by schema

Give each schema its own folder with its own changelog, so a deployment to one schema never picks up another schema's objects. Group the SQL files by object type, release, or a pattern of your choosing inside each schema folder, and keep the flow files, checks settings, and CI/CD definitions in their own top-level folders.

loading

The root changelog.main.yaml includes one changelog per schema and labels each one, so a run filtered to a single schema picks up only that schema's changesets.

Be sure to:

  • Add one include block per schema you manage

loading

In this example, each schema's changelog.yaml picks up its object folders in dependency order. Tables come before the views, functions, and procedures that reference them, and data loads come last.

Be sure to:

  • Replace SCHEMA_A in the labels values with the schema this changelog belongs to

loading
10

Deploy to each schema with a flow file

Liquibase runs once per schema rather than against all schemas at once, so each schema gets its own connection, status check, and report. A flow file drives that loop, which keeps the deployment identical across Jenkins, GitHub Actions, GitLab CI, and Azure DevOps. Each platform only has to check out the repository, supply the secrets as environment variables, and call liquibase flow.

Set the inputs and run the multischema flow.

Be sure to:

  • Replace SCHEMA_A,SCHEMA_B with your own comma-separated schema list

  • Replace liquibase_proxy with your proxy user name

  • Replace LIQUIBASE with your tracking schema name

  • Supply LIQUIBASE_COMMAND_PASSWORD from your CI system's secret store, never from the properties file

  • Use liquibase-multischema.windows.flowfile.yaml instead if you run on Windows

loading

The multischema flow loops over LB_SCHEMAS and calls the per-schema update flow once for each one, overriding the proxy target, the default schema, and the changelog on every pass. The tracking schema stays constant, which is what keeps every schema reporting into one deployment history.

loading

The per-schema flow it calls, liquibase-update.flowfile.yaml, runs validate, then status, then policy checks, then an optional dry run, then tag and update, and always finishes with history. Set LB_DRY_RUN to true to produce the SQL and the report without deploying.

Because the flow files hold all of the deployment logic, the CI/CD job itself stays small. It only has to check out the repository, supply the secrets as environment variables, and call liquibase flow. A worked Jenkins example lives alongside the flows as cicd/Jenkinsfile_update.groovy.