Skip to content

Container Databases (CDB) and Pluggable Databases (PDBs)

Overview

In this workshop you will:
- Understand Oracle’s multitenant architecture (CDB$ROOT, PDB$SEED, and PDBs).
- Perform core operations on pluggable databases (open/close, switch container, create/drop).
- Manage common users (C##...) vs local users (PDB-scoped) and practice privilege assignment.

Notation
- Terminal: commands in bash blocks (prompt example: [oracle@dbserver ~]$).
- SQL*Plus: commands in sql blocks (run after connecting with sqlplus).

Quick reference: Operations on PDBs

These are the core operations you will use to manage pluggable databases. Replace:
- pdb_name with your PDB name (in this workshop: ORCLPDB1).
- admin / password with the local admin credentials for a new PDB.
- '/target_path/' with a server-side directory where Oracle can create the PDB datafiles.

SQL*Plus:

-- Open a PDB (make it available for connections)
ALTER PLUGGABLE DATABASE pdb_name OPEN;

-- Close a PDB
ALTER PLUGGABLE DATABASE pdb_name CLOSE;

-- "Connect" to a PDB inside the same SQL*Plus session (switch current container)
ALTER SESSION SET CONTAINER = pdb_name;

-- Create a new PDB (typically cloned from PDB$SEED)
CREATE PLUGGABLE DATABASE pdb_name
  ADMIN USER admin IDENTIFIED BY password
  ROLES = (DBA)
  CREATE_FILE_DEST = '/target_path/';

-- Drop a PDB and delete its datafiles (irreversible)
DROP PLUGGABLE DATABASE pdb_name INCLUDING DATAFILES;

Notes:
- OPEN / CLOSE / CREATE / DROP are typically run while connected as SYSDBA in CDB$ROOT.
- ALTER SESSION SET CONTAINER = ... switches containers; it does not use the listener. To connect directly as an application user, use the PDB service (for example: sqlplus app_user/app_pass@localhost:1521/orclpdb1).


1. Oracle multitenant architecture

In Oracle’s multitenant architecture, a Container Database (CDB) comprises:
- Root Container (CDB$ROOT): system metadata, common data dictionary views, and global information shared by all PDBs.
- Seed Container (PDB$SEED): a read‑only template used to create new PDBs.
- Pluggable Databases (PDBs): isolated databases hosted inside the CDB (PDB1, PDB2, …).

Cette architecture améliore l'extensibilité et permet une gestion simplifiée des bases de données dans un environnement consolidé.

CDB
├── CDB$ROOT
├── PDB$SEED   (read-only template)
├── PDB1
├── PDB2
└── PDBn

2. Pluggable Database (PDB)

A PDB is an autonomous database hosted inside a CDB. It has its own:
- Schemas
- Tables
- Objects
- User sessions

Because each PDB is isolated, you can:
- Add/drop/migrate PDBs more easily.
- Partition resources efficiently.
- Simplify upgrades, backups and lifecycle operations.


3. Operations on PDBs

3.1 Identify the current container and list PDBs

SHOW CON_NAME
SHOW PDBS

3.2 Open and close a PDB

ALTER PLUGGABLE DATABASE pdb_name OPEN;
ALTER PLUGGABLE DATABASE pdb_name CLOSE;

3.3 Connect (switch) to a PDB

ALTER SESSION SET CONTAINER = pdb_name;

3.4 Create a PDB (from PDB$SEED)

CREATE PLUGGABLE DATABASE pdb_name
  ADMIN USER admin IDENTIFIED BY password
  ROLES = (DBA)
  CREATE_FILE_DEST = '/target_path/';

Notes:
- CREATE_FILE_DEST must point to a valid server-side directory where Oracle can create the PDB datafiles.
- The exact clause(s) can vary depending on your lab setup (FILE_NAME_CONVERT, PATH_PREFIX, etc.).

3.5 Drop a PDB

DROP PLUGGABLE DATABASE pdb_name INCLUDING DATAFILES;

4. Users in a CDB: common vs local

Oracle distinguishes between common users and local users:

4.1 Common users (C##...)

A common user exists across the whole CDB (in CDB$ROOT and all PDBs):
- Naming convention: must start with C## (for example C##test).
- Created in CDB$ROOT.
- Privileges are granted per container (a common user can be powerful in one PDB and restricted in another).

Example:

-- Run in CDB$ROOT
CREATE USER c##admin_user IDENTIFIED BY password;

Granting a privilege in a specific PDB:

ALTER SESSION SET CONTAINER = pdb_name;
GRANT CREATE SESSION TO c##admin_user;

4.2 Local users (PDB-scoped)

A local user is created inside one PDB and exists only in that PDB:
- It does not exist in CDB$ROOT or other PDBs.
- Ideal for application users and end users that should remain inside one database.

Example:

-- Run after switching to a PDB
ALTER SESSION SET CONTAINER = pdb_name;
CREATE USER app_user IDENTIFIED BY app_pass;

5. Exercises (assume ORCLPDB1)

We will assume that our pluggable database of study is ORCLPDB1 (service: orclpdb1).

Exercise 1: Create a common user and a local user

Objective
- Create a common user C##test in CDB$ROOT and grant it CREATE SESSION in the root.
- Create a local user app_user in ORCLPDB1 and grant it the DBA role.

Steps

1) Connect as SYSDBA and confirm you are in the root:
Terminal ([oracle@dbserver ~]$):

sqlplus / AS SYSDBA

SQL*Plus:

SHOW CON_NAME

2) Create the common user in CDB$ROOT:

ALTER SESSION SET CONTAINER = CDB$ROOT;

CREATE USER c##test IDENTIFIED BY test;
GRANT CREATE SESSION TO c##test;

3) Create the local user in ORCLPDB1:

ALTER SESSION SET CONTAINER = ORCLPDB1;

CREATE USER app_user IDENTIFIED BY app_pass;
GRANT DBA TO app_user;

Verification

From the root, list common users:

ALTER SESSION SET CONTAINER = CDB$ROOT;
SELECT username, common FROM cdb_users WHERE common = 'YES';

In ORCLPDB1, list users and check roles/privileges:

ALTER SESSION SET CONTAINER = ORCLPDB1;
SELECT username FROM dba_users;

SELECT * FROM dba_role_privs WHERE grantee IN ('APP_USER','C##TEST');
SELECT * FROM dba_sys_privs  WHERE grantee IN ('APP_USER','C##TEST');

Exercise 2: Privileges on tables (common user vs local user)

Tables used in this exercise:

CREATE TABLE departments (
  dept_id    NUMBER PRIMARY KEY,
  dept_name  VARCHAR2(100)
);

CREATE TABLE employees (
  emp_id     NUMBER PRIMARY KEY,
  name       VARCHAR2(50),
  dept_id    NUMBER,
  email      VARCHAR2(100),
  join_date  DATE,
  CONSTRAINT fk_emp_dept
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);

Objective
- Create the tables and insert some rows as app_user.
- Give C##test the ability to view EMPLOYEES.
- Create a local user test in ORCLPDB1 and give it the ability to view and modify both tables.
- Connect as C##test and test and display the tables according to the granted privileges.

Steps

1) Start the listener:
- To return from SQL*Plus to the terminal, use EXIT in SQL*Plus.

Terminal ([oracle@dbserver ~]$):

lsnrctl start

2) Check listener status (verify it is running on the database server):

Terminal ([oracle@dbserver ~]$):

lsnrctl status

From lsnrctl status, confirm:
- The listener is READY and listening on the expected port (usually 1521).
- The orclpdb1 service appears under Services Summary (service registration).

If orclpdb1 is missing from the services list, open it and force registration (as SYSDBA):

ALTER PLUGGABLE DATABASE ORCLPDB1 OPEN;
ALTER SYSTEM REGISTER;

3) Ensure C##test can connect to ORCLPDB1 (privileges are container-specific):

ALTER SESSION SET CONTAINER = ORCLPDB1;
GRANT CREATE SESSION TO c##test;

4) Connect to ORCLPDB1 through the listener as app_user, then create and populate tables:
Terminal ([oracle@dbserver ~]$):

sqlplus app_user/app_pass@localhost:1521/orclpdb1

SQL*Plus:

CREATE TABLE departments (
  dept_id    NUMBER PRIMARY KEY,
  dept_name  VARCHAR2(100)
);

CREATE TABLE employees (
  emp_id     NUMBER PRIMARY KEY,
  name       VARCHAR2(50),
  dept_id    NUMBER,
  email      VARCHAR2(100),
  join_date  DATE,
  CONSTRAINT fk_emp_dept
    FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
);

INSERT INTO departments VALUES (10, 'Sales');
INSERT INTO departments VALUES (20, 'IT');

INSERT INTO employees VALUES (1, 'Ahmed',  10, 'ahmed@sales.company.com', DATE '2020-05-10');
INSERT INTO employees VALUES (2, 'Ali',    10, 'ali@sales.company.com',   DATE '2021-01-15');
INSERT INTO employees VALUES (3, 'Mohamed', 20, 'mohamed@it.company.com',   DATE '2019-03-22');

COMMIT;

5) As app_user, grant privileges:

GRANT SELECT ON employees TO c##test;

CREATE USER test IDENTIFIED BY test;
GRANT CREATE SESSION TO test;
GRANT SELECT, INSERT, UPDATE, DELETE ON departments TO test;
GRANT SELECT, INSERT, UPDATE, DELETE ON employees   TO test;

6) As C##test, connect through the listener and view EMPLOYEES:
Terminal ([oracle@dbserver ~]$):

sqlplus c##test/test@localhost:1521/orclpdb1

SQL*Plus:

SELECT * FROM app_user.employees;

7) As test, connect through the listener, then view and modify the tables:
Terminal ([oracle@dbserver ~]$):

sqlplus test/test@localhost:1521/orclpdb1

SQL*Plus:

SELECT * FROM app_user.departments;
SELECT * FROM app_user.employees;

UPDATE app_user.departments
SET    dept_name = 'Information Technology'
WHERE  dept_id = 20;

COMMIT;

Verification

As a DBA in ORCLPDB1, check grants for C##TEST and TEST:

ALTER SESSION SET CONTAINER = ORCLPDB1;
SELECT owner, table_name, grantee, privilege
FROM   dba_tab_privs
WHERE  owner = 'APP_USER'
  AND  grantee IN ('C##TEST','TEST')
ORDER BY table_name, grantee, privilege;

Verify which common users have privileges on EMPLOYEES:

SELECT grantee, privilege
FROM   dba_tab_privs
WHERE  owner = 'APP_USER'
  AND  table_name = 'EMPLOYEES'
  AND  grantee LIKE 'C##%';

6. Takeaways

  • A CDB is made of CDB$ROOT, PDB$SEED, and multiple PDBs.
  • PDB operations are done with ALTER PLUGGABLE DATABASE ... and ALTER SESSION SET CONTAINER = ....
  • Common users (C##...) exist in all containers, but privileges are still granted per container.
  • Local users exist only in one PDB, which is ideal for application isolation.