03 · Storage Credentials, External Locations & Volumes¶
Unity Catalog can govern tables and files only if it controls the door to the storage. That door is built from three objects: a storage credential (a cloud identity), an external location (a path plus a credential) and the privileges on them. Volumes then give governed, path-based access to files.
What was run
Nothing here touches a real cloud account or workspace. The facts come from the Databricks documentation pages on
external locations, storage credentials, managed storage, volumes and "path overlap restrictions" (October 2026),
for AWS; Azure and GCP use different credential types but the same ideas. The SQL statements couldn't be validated
by sqlglot (it treats Unity Catalog DDL as opaque commands), so I checked their syntax against the SQL reference.
The one program in this lesson is a small model I wrote of the documented no-overlap rule for external locations;
it is a teaching aid, not Databricks code.
The three objects¶
cloud IAM role / managed identity ──► STORAGE CREDENTIAL (who UC acts as when touching storage)
+ a path ► EXTERNAL LOCATION (where, and which credential)
+ grants ► READ FILES / WRITE FILES / CREATE EXTERNAL TABLE / CREATE EXTERNAL VOLUME / CREATE MANAGED STORAGE
- A storage credential wraps a cloud identity: on AWS an IAM role that Databricks can assume; on Azure a managed
identity or service principal; on GCP a service account. Databricks recommends granting only
CREATE EXTERNAL LOCATIONon credentials, and no direct use. - An external location binds a storage path (like
s3://acme-raw/landing) to a credential. It is the unit you grant access on. - On AWS you create the credential first, then the location. The documentation prerequisites include: the bucket must
exist; its name can't contain dots; the path must use only standard ASCII characters; and the bucket cannot have an S3
ACL attached. You need
CREATE STORAGE CREDENTIALon the metastore for the first, andCREATE EXTERNAL LOCATIONon both the metastore and the credential for the second (metastore admins have those on the metastore by default).
-- Credentials are normally created in Catalog Explorer or through the API (they need cloud-side IAM setup);
-- then, in SQL:
CREATE EXTERNAL LOCATION IF NOT EXISTS landing
URL 's3://acme-raw/landing'
WITH (STORAGE CREDENTIAL acme_raw_cred)
COMMENT 'Raw drops from partners';
DESCRIBE EXTERNAL LOCATION landing;
GRANT READ FILES, WRITE FILES ON EXTERNAL LOCATION landing TO `ingest-service`;
GRANT CREATE EXTERNAL TABLE ON EXTERNAL LOCATION landing TO `data-engineers`;
SHOW STORAGE CREDENTIALS;
SHOW GRANTS ON EXTERNAL LOCATION landing;
(CREATE EXTERNAL LOCATION ... URL ... WITH (STORAGE CREDENTIAL ...), DESCRIBE EXTERNAL LOCATION, SHOW STORAGE
CREDENTIALS and SHOW GRANTS ... ON EXTERNAL LOCATION match the reference pages; the bucket and credential names are
invented.) One documented gotcha: WRITE FILES requires READ FILES on the same location, because writes involve
metadata checks and path validation; a user with only WRITE FILES gets PERMISSION_DENIED. Grant both together.
The documentation also recommends using volumes to govern file access, rather than granting READ FILES directly to many users.
Path rules: no overlaps¶
Unity Catalog guarantees that two securables never govern the same bytes, via documented rules. External locations can't overlap other external locations; volumes can't overlap volumes; tables can't overlap tables; tables and volumes can't overlap each other; managed storage locations can't overlap each other, external tables or external volumes. Consequently you can't define an external location inside another one, can't register a table on files inside a volume, and can't define a volume on a directory inside a table. Files inside a volume can always be read and written by path (including Delta files), but they cannot be registered as tables.
Here is a tiny model of the first two rules. It registers locations (refusing overlaps in either direction) and finds which one governs a path:
"""Teaching model of one documented Unity Catalog rule: external locations can't overlap, and a path is
governed by the single external location that contains it. Not Databricks code."""
def contains(base, path):
base = base.rstrip("/") + "/"; return (path.rstrip("/") + "/").startswith(base)
class Metastore:
def __init__(self): self.locations = {}
def create_external_location(self, name, url):
clash = [n for n, u in self.locations.items() if contains(u, url) or contains(url, u)]
if clash: raise ValueError(f"cannot register {url!r}: overlaps external location(s) {clash}")
self.locations[name] = url
def governing(self, path):
hits = [n for n, u in self.locations.items() if contains(u, path)]
return hits[0] if hits else None
ms = Metastore()
ms.create_external_location("landing", "s3://acme-raw/landing")
ms.create_external_location("curated", "s3://acme-curated")
for p in ["s3://acme-raw/landing/orders/2026-10-21/f.json", "s3://acme-raw/other/x.csv",
"s3://acme-curated/sales/2026/part.parquet", "s3://acme-raw/landing-archive/x"]:
print(f"{p:<50} -> {ms.governing(p) or 'no external location: access denied'}")
for name, url in [("sales", "s3://acme-curated/sales"), ("raw_all", "s3://acme-raw"), ("archive", "s3://acme-raw/landing-archive")]:
try: ms.create_external_location(name, url); print(f"registered {name}: {url}")
except ValueError as e: print("REJECTED:", e)
s3://acme-raw/landing/orders/2026-10-21/f.json -> landing
s3://acme-raw/other/x.csv -> no external location: access denied
s3://acme-curated/sales/2026/part.parquet -> curated
s3://acme-raw/landing-archive/x -> no external location: access denied
REJECTED: cannot register 's3://acme-curated/sales': overlaps external location(s) ['curated']
REJECTED: cannot register 's3://acme-raw': overlaps external location(s) ['landing']
registered archive: s3://acme-raw/landing-archive
Notice the string-prefix trap the model has to avoid: landing-archive starts with the characters landing but is a
sibling folder, not a child. Comparing paths by startswith without the trailing / would wrongly treat them as
overlapping; the first version of my model handled that by adding a separator before comparing. Overlap planning matters in
practice: pick location boundaries before you create them (a bucket-wide location blocks every future per-folder
location), and create external tables and volumes in sub-directories, not at the root of a location, as the docs advise.
Volumes in depth¶
A volume is a governed directory in a schema. Managed volumes live in the schema's managed storage; external volumes point at a path in an external location.
CREATE VOLUME prod.raw.landing_managed COMMENT 'Staging area';
CREATE EXTERNAL VOLUME prod.raw.partner_drops
LOCATION 's3://acme-raw/landing/partners' COMMENT 'Files delivered by partners';
GRANT READ VOLUME ON VOLUME prod.raw.partner_drops TO `analysts`;
GRANT WRITE VOLUME ON VOLUME prod.raw.partner_drops TO `ingest-service`;
Per the reference, CREATE VOLUME needs CREATE VOLUME on the schema, and creating an external volume also needs CREATE
EXTERNAL VOLUME on the external location. Dropping an external volume leaves the files; dropping a managed volume deletes
its data (with a 7-day retention per the volumes documentation).
Access paths:
| From | Path |
|---|---|
Spark, SQL, pandas, dbutils.fs, shell, %sh |
/Volumes/<catalog>/<schema>/<volume>/<path> |
| Apps and REST | Files API: /api/2.0/fs/files/Volumes/... |
| Cloud URI | Possible for external volumes with sufficient privileges; Unity Catalog privileges override those on the external location |
The cardinal rule: don't bypass UC¶
The documentation is blunt: do not give non-UC identities storage-level access to managed tables or volumes. Anyone with direct bucket access bypasses UC's access control, escapes auditing and lineage, and can corrupt tables by writing files Delta doesn't know about. The only identity that should touch managed storage is the one UC uses. For external data that other systems also use, grant those systems access to their path through your cloud IAM, but make Databricks users go through UC.
How It Actually Works¶
When compute needs s3://acme-raw/landing/orders/f.json, it presents the user's identity to UC. UC finds the governing
external location (or the table/volume that maps to the path), checks the user's privileges on that object, then asks the
cloud provider to assume the storage credential's role with a session policy narrowed to that path, and returns the
resulting short-lived token to the compute. The engine reads with that token. Because credentials are minted per request,
scoped and expiring, nothing long-lived is ever on a cluster, and revoking a grant takes effect at the next request.
A managed table path is a generated random directory (the docs say the name is randomly generated), which is why path-based access to managed tables isn't supported: use the table name.
Common mistakes¶
- Registering an external location at a bucket root, blocking every smaller location later.
- Granting
WRITE FILESwithoutREAD FILESand gettingPERMISSION_DENIED. - Giving users direct S3/ADLS access to the buckets behind managed storage.
- Trying to create a table on files inside a volume (not allowed; load them into a table).
- Granting
READ FILESon locations broadly instead of governing through volumes and tables. - Dots in S3 bucket names, or non-ASCII paths, which the AWS prerequisites exclude.
- One credential for everything, so a compromise (or mistake) has maximum reach.
Exercise¶
- Add a method to the model that finds all objects a given path would overlap, and test it with a child, a parent and a
sibling path (
landing,landing/x,landing-archive). - Plan the external locations for three buckets (
raw,curated,sandbox) such that no overlap will block you later. Which privileges do the ingest job and the analysts need? - Explain why granting analysts
READ FILESon a location is weaker than giving themREAD VOLUMEon a volume. - List three ways a person with direct bucket access could damage a managed table without Unity Catalog noticing.