- 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.jarThe reference project this guide is based on: cs_oracle_proxy
Procedure
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_proxywith the proxy user name you want. For example,LIQUIBASE_PROXYReplace
liquibase_proxy_pwwith the password for that userReplace
userswith your tablespace name
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_AandSCHEMA_Bwith your application schema namesReplace
LIQUIBASEwith the name you want for the tracking schema
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_ROLEwith the role name you wantAdd a
GRANT LIQUIBASE_ROLE TOline for every schema you manage
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.
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.
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_TESTwith your own host, port, and service nameSet
liquibase.command.passwordin 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 statusGrant 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
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 SYNONYMprivilege, such asSYSDBA
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
urlhost, port, and service name with your ownReplace
LIQUIBASEwith your tracking schema nameReplace
SCHEMA_Awith the schema you want to deploy to by default
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.
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
includeblock per schema you manage
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_Ain thelabelsvalues with the schema this changelog belongs to
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_Bwith your own comma-separated schema listReplace
liquibase_proxywith your proxy user nameReplace
LIQUIBASEwith your tracking schema nameSupply
LIQUIBASE_COMMAND_PASSWORDfrom your CI system's secret store, never from the properties fileUse
liquibase-multischema.windows.flowfile.yamlinstead if you run on Windows
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.
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.