Blob Storage
On this page
- Provide Credentials Using Mounted Files
- Provide Credentials Using Kubernetes Secrets
- Update Blob Storage Configuration After Installation
- Provide License Using a Kubernetes Secret
- Use s3a:// Warehouses for Paimon and Iceberg Catalogs
- Use Separate Credentials for Multiple S3 Buckets
- Apply Per-Bucket Credentials to a Deployment
- Apply Per-Bucket Credentials to a Session Cluster
- Secret Value Behavior and Limitations
- Scope of the Per-Bucket Keys
Provide Credentials Using Mounted Files
Instead of embedding blob storage credentials directly in your Helm values.yaml, you can provide them as files mounted into the Ververica Platform container. Ververica Platform reads the credential files from a configured directory at startup and distributes the values to services that require them.
This approach keeps credentials out of your Helm values, which might be stored in version control or visible to operators who have access to the Helm release.
How It Works
Each credential is stored in a separate file. Ververica Platform discovers credentials by scanning the configured directory and reading every file whose name matches the pattern <provider>.<key>. Each file must contain exactly one value: the raw credential string with no additional formatting.
Example: S3 Credentials
Create one file per credential in your credentials directory:
1s3.accessKeyIdThe file name determines which provider and key the value is assigned to. The file content is the credential value.
Configuration
Mount your credentials directory into the Ververica Platform pod and set the path in your values.yaml:
1global:
2 blobStorage:
3 credentialsDir: /conf/blob-credsReplace /conf/blob-creds with the path where your credentials files are mounted inside the container.
File names must follow the <provider>.<key> pattern exactly. Ververica Platform silently ignores files that do not match the pattern. Incorrectly named files are not loaded and the corresponding credentials are unavailable. Verify file names if credentials are not being picked up.
Provide Credentials Using Kubernetes Secrets
Instead of mounting credential files, you can store blob storage credentials in a Kubernetes Secret and reference the Secret by name in your values.yaml. Ververica Platform reads the credentials from the Secret at startup and distributes them to services that require them.
This approach integrates with Kubernetes-native secret management and is compatible with tools like Sealed Secrets, External Secrets Operator, or Vault agent injection.
Create the Secret
Create a Kubernetes Secret with one key per credential. Key names must follow the <provider>.<key> pattern, using the same convention as mounted credential files.
For S3 credentials:
1kubectl create secret generic blob-storage-credentials \
2 --from-literal=s3.accessKeyId=AKIAEXAMPLEACCESSKEYID \
3 --from-literal=s3.secretAccessKey=wJalrXUtnFEMIEXAMPLEKEYsecretkey \
4 --namespace vvp-systemReference the Secret in Your Values
Set the secret name in your values.yaml:
1global:
2 blobStorage:
3 credentialsSecret: blob-storage-credentials Replace blob-storage-credentials with the name of your Secret and vvp-system with the namespace where Ververica Platform is installed.
Secret key names must follow the <provider>.<key> pattern exactly. Ververica Platform silently ignores keys that do not match the pattern. Verify key names if credentials are not being picked up.
Update Blob Storage Configuration After Installation
You can change blob storage configuration after the initial Helm installation without performing a full reinstall. Run helm upgrade with your updated values.yaml:
1helm upgrade --install <RELEASE_NAME> \
2 oci://registry.ververica.cloud/platform-charts/ververica-platform \
3 --version <VERSION> \
4 --namespace vvp-system \
5 --values values.yamlReplace <RELEASE_NAME> with your Helm release name and <VERSION> with the installed platform version.
Updating blob storage configuration triggers a restart of the Ververica Platform pods. Running Flink jobs are not affected, but avoid making configuration changes while deployments are actively transitioning state.
Provide License Using a Kubernetes Secret
Instead of embedding your Ververica Platform license directly in values.yaml, you can supply it as a Kubernetes Secret mounted into the platform pods. This keeps the license out of Helm configuration files, which may be stored in version control or exposed through CI/CD pipelines.
Create the Secret
Create a Kubernetes Secret containing your license file:
1kubectl create secret generic vvp-license \
2 --from-file=license.yaml=/path/to/your/license.yaml \
3 --namespace vvp-systemReference the Secret in Your Values
Set the secret name in your values.yaml:
1global:
2 license:
3 existingSecret: vvp-license Replace vvp-license with the name of your Secret and vvp-system with the namespace where Ververica Platform is installed.
Use s3a:// Warehouses for Paimon and Iceberg Catalogs
Catalogs that resolve storage through the Hadoop file system API, including Apache Paimon and Apache Iceberg with catalog-type=hadoop, address their warehouse with the s3a:// scheme. The class that implements this scheme, org.apache.hadoop.fs.s3a.S3AFileSystem, ships in flink-s3-fs-hadoop, which is loaded by the isolated plugin class loader. The Hadoop file system API loads from the main class loader, so without additional handling a catalog on s3a:// fails with a ClassNotFoundException.
Ververica Platform places the required class on the SQL Gateway class path when the gateway starts. This is controlled by the following value, which is enabled by default:
1vvp-appagent:
2 sqlService:
3 s3aCompatibility:
4 enabled: trueSet enabled: false to keep the main class path free of the unshaded Hadoop S3 file system. Catalogs on s3a:// then fail to resolve.
The class is added to the extracted engine distribution used by the SQL Gateway only. Deployment and session cluster class paths are not modified.
Create a Paimon Catalog on s3a://
Supply the storage credentials and endpoint in the catalog WITH clause:
1CREATE CATALOG paimon_cat WITH (
2 'type' = 'paimon',
3 'metastore' = 'filesystem',
4 'warehouse' = 's3a://<BUCKET>/<PATH>',
5 'fs.s3a.endpoint' = 'https://<S3_ENDPOINT>',
6 'fs.s3a.access.key' = '<ACCESS_KEY>',
7 'fs.s3a.secret.key' = '<SECRET_KEY>',
8 'fs.s3a.path.style.access' = 'true'
9);The configuration is applied at gateway startup, so it survives a restart of the SQL Gateway without being reapplied.
The s3i:// scheme is internal to Ververica Platform blob storage and is not a Hadoop file system scheme. A Paimon or Iceberg catalog cannot use s3i:// as its warehouse location, and reports that no file system implementation was found for the scheme. Use s3a:// or s3:// for catalog warehouses.
Storage Behind a Private Certificate Authority
When the S3 endpoint presents a certificate signed by an internal authority, the JobManager and TaskManager must trust that authority, otherwise the connection fails with a PKIX path validation error. Deliver the trust store to Flink pods through the platform-wide deployment defaults rather than per deployment. See Platform-Wide Deployment Defaults (publishing pending review) for the private-certificate-authority walkthrough.
Use Separate Credentials for Multiple S3 Buckets
A deployment can read from and write to several S3 buckets, each authenticating with its own credentials. Configure this once per namespace, and every deployment in that namespace can use it.
Configuration Keys
Set the following keys in flinkConfiguration for each bucket:
1s3.bucket.<bucket-name>.access.key: <access key>
2s3.bucket.<bucket-name>.secret.key: <secret key>
3s3.bucket.<bucket-name>.endpoint: <https endpoint> # omit for AWS S3
4s3.bucket.<bucket-name>.path.style.access: "true" # omit for AWS S3Repeat the block for every bucket. For example, two buckets, each with its own credentials:
1s3.bucket.bucket-one.access.key: userA
2s3.bucket.bucket-one.secret.key: ${secret_values.s3_bucket_one_secret}
3s3.bucket.bucket-one.endpoint: https://s3.internal.example.com:9000
4s3.bucket.bucket-one.path.style.access: "true"
5
6s3.bucket.bucket-two.access.key: userB
7s3.bucket.bucket-two.secret.key: ${secret_values.s3_bucket_two_secret}
8s3.bucket.bucket-two.endpoint: https://s3.internal.example.com:9000
9s3.bucket.bucket-two.path.style.access: "true"A single job can then write to both buckets, each one authenticating with its own credentials:
1CREATE TEMPORARY TABLE sink_one (id INT, v STRING) WITH (
2 'connector' = 'filesystem', 'path' = 's3a://bucket-one/data', 'format' = 'json');
3
4CREATE TEMPORARY TABLE sink_two (id INT, v STRING) WITH (
5 'connector' = 'filesystem', 'path' = 's3a://bucket-two/data', 'format' = 'json');bucket.must appear literally in the key.s3.<bucket-name>.access.key, without it, is ignored with no warning.- Use the dot form,
access.keyandsecret.key. The dash form applies only to the global keys, not the per-bucket ones. - Table paths must use
s3a://, for example'path' = 's3a://bucket-one/dir'.
Apply Per-Bucket Credentials to a Deployment
The examples below configure two buckets, each with its own credentials, matching the example above. Set these values once, and the rest of the examples can be pasted unchanged:
1PLATFORM_HOST="https://<platform-host>"
2TOKEN="<api-token>"
3NAMESPACE="<namespace>"
4S3_ENDPOINT="https://<s3-endpoint>"Step 1: Store Each Secret Key as a Secret Value
Store one secret value per bucket, following Secret Values, so that no secret key is written directly into the deployment configuration:
1curl -X POST "$PLATFORM_HOST/api/v1/namespaces/$NAMESPACE/secret-values" \
2 -H "Authorization: Bearer $TOKEN" \
3 -H "workspace: defaultworkspace" \
4 -H "Content-Type: application/json" \
5 -d '{
6 "kind": "SecretValue",
7 "apiVersion": "v1",
8 "metadata": { "name": "s3_bucket_one_secret", "namespace": "'"$NAMESPACE"'", "workspace": "defaultworkspace" },
9 "spec": { "kind": "Plain", "value": "<secret key of bucket-one>" }
10 }'
11
12curl -X POST "$PLATFORM_HOST/api/v1/namespaces/$NAMESPACE/secret-values" \
13 -H "Authorization: Bearer $TOKEN" \
14 -H "workspace: defaultworkspace" \
15 -H "Content-Type: application/json" \
16 -d '{
17 "kind": "SecretValue",
18 "apiVersion": "v1",
19 "metadata": { "name": "s3_bucket_two_secret", "namespace": "'"$NAMESPACE"'", "workspace": "defaultworkspace" },
20 "spec": { "kind": "Plain", "value": "<secret key of bucket-two>" }
21 }'Secret values can also be created from the platform UI.
Step 2: Apply the Keys to the Deployment Defaults
Deployment defaults are stored per deployment type, addressed by a path segment. Each type reads only its own object, so apply the keys to every deployment type in use.
Stream deployments:
1curl -X PATCH "$PLATFORM_HOST/api/v1/namespaces/$NAMESPACE/deployment-defaults/stream" \
2 -H "Authorization: Bearer $TOKEN" \
3 -H "workspace: defaultworkspace" \
4 -H "Content-Type: application/json" \
5 -d '{
6 "spec": {
7 "template": {
8 "spec": {
9 "flinkConfiguration": {
10 "s3.bucket.bucket-one.access.key": "<access key of bucket-one>",
11 "s3.bucket.bucket-one.secret.key": "${secret_values.s3_bucket_one_secret}",
12 "s3.bucket.bucket-one.endpoint": "'"$S3_ENDPOINT"'",
13 "s3.bucket.bucket-one.path.style.access": "true",
14
15 "s3.bucket.bucket-two.access.key": "<access key of bucket-two>",
16 "s3.bucket.bucket-two.secret.key": "${secret_values.s3_bucket_two_secret}",
17 "s3.bucket.bucket-two.endpoint": "'"$S3_ENDPOINT"'",
18 "s3.bucket.bucket-two.path.style.access": "true"
19 }
20 }
21 }
22 }
23 }'Batch deployments:
1curl -X PATCH "$PLATFORM_HOST/api/v1/namespaces/$NAMESPACE/deployment-defaults/batch" \
2 -H "Authorization: Bearer $TOKEN" \
3 -H "workspace: defaultworkspace" \
4 -H "Content-Type: application/json" \
5 -d '{
6 "spec": {
7 "template": {
8 "spec": {
9 "flinkConfiguration": {
10 "s3.bucket.bucket-one.access.key": "<access key of bucket-one>",
11 "s3.bucket.bucket-one.secret.key": "${secret_values.s3_bucket_one_secret}",
12 "s3.bucket.bucket-one.endpoint": "'"$S3_ENDPOINT"'",
13 "s3.bucket.bucket-one.path.style.access": "true",
14
15 "s3.bucket.bucket-two.access.key": "<access key of bucket-two>",
16 "s3.bucket.bucket-two.secret.key": "${secret_values.s3_bucket_two_secret}",
17 "s3.bucket.bucket-two.endpoint": "'"$S3_ENDPOINT"'",
18 "s3.bucket.bucket-two.path.style.access": "true"
19 }
20 }
21 }
22 }
23 }'A PATCH merges: the keys you send are added or updated, and keys already stored but not sent are left unchanged. To remove a key, edit the block from the UI instead of omitting it from a PATCH.
In the UI, the same blocks are under Configurations, on the Deployment Defaults tab, which has one tab per deployment type. A ?deploymentType=<type> query parameter is accepted but ignored: the request always reads and writes the stream object. Always address the type through the path segment.
Step 3: Verify What Was Stored
1curl -s "$PLATFORM_HOST/api/v1/namespaces/$NAMESPACE/deployment-defaults/stream" \
2 -H "Authorization: Bearer $TOKEN" \
3 -H "workspace: defaultworkspace" \
4 | python3 -c "import json,sys; d=json.load(sys.stdin); fc=d['spec']['template']['spec']['flinkConfiguration']; print(d['metadata']['name'], {k:v for k,v in fc.items() if k.startswith('s3.bucket')})"metadata.name in the response must match the type you intended, and each secret.key must show the ${secret_values....} reference rather than a plaintext value.
Defaults apply only when a deployment is created. Existing deployments don't pick up later changes: add the keys to the deployment itself, or recreate it. A key set on a deployment overrides the namespace default.
An S3 endpoint served with a certificate from a private authority needs that authority in the trust store used by the Flink JVMs. See “Storage Behind a Private Certificate Authority” earlier on this page.
Apply Per-Bucket Credentials to a Session Cluster
Namespace deployment defaults don't apply to session clusters. Set the same keys in the Session Clusters own flinkConfiguration instead.
For an existing session cluster, add the keys to its configuration from the UI and restart it. Values are read at JVM start, so a restart is required after any change.
Through the API, the request body must be complete: resources, numberOfTaskManagers, and logging are mandatory, and the engine image coordinates must be supplied too, because a session cluster doesn't resolve them automatically the way a deployment does:
1curl -X POST "$PLATFORM_HOST/api/v1/workspaces/defaultworkspace/namespaces/$NAMESPACE/sessionclusters" \
2 -H "Authorization: Bearer $TOKEN" \
3 -H "workspace: defaultworkspace" \
4 -H "Content-Type: application/json" \
5 -d '{
6 "metadata": { "name": "<session-cluster-name>", "namespace": "'"$NAMESPACE"'" },
7 "spec": {
8 "state": "RUNNING",
9 "deploymentTargetName": "<deployment-target>",
10 "flinkVersion": "1.20",
11 "flinkImageRegistry": "<registry>",
12 "flinkImageRepository": "<repository>",
13 "flinkImageTag": "<engine image tag>",
14 "numberOfTaskManagers": 1,
15 "resources": {
16 "jobmanager": { "cpu": 0.3, "memory": "1.5Gi" },
17 "taskmanager": { "cpu": 0.3, "memory": "1.5Gi" }
18 },
19 "logging": { "loggingProfile": "default" },
20 "flinkConfiguration": {
21 "s3.bucket.bucket-one.access.key": "<access key of bucket-one>",
22 "s3.bucket.bucket-one.secret.key": "${secret_values.s3_bucket_one_secret}",
23 "s3.bucket.bucket-one.endpoint": "'"$S3_ENDPOINT"'",
24 "s3.bucket.bucket-one.path.style.access": "true"
25 }
26 }
27 }'The image coordinates must match the engine version in use. Copy them from an existing session cluster or from the UI.
Two behaviors to expect on this version. A body missing flinkVersion and the flinkImage* fields is still accepted, but the resulting session cluster can't start and stays in STOPPING. The create call can also return HTTP 400 even though the session cluster is created correctly, so confirm the outcome by listing the session clusters rather than relying on the status code.
Secret Value Behavior and Limitations
- The defaults object, the deployment, and the job store only the placeholder, so the value itself is never exposed through the APIs or the configuration views.
- The value is substituted when the Flink configuration is rendered, and the key is added to
sensitive-keys.additional, so Flink masks it in its own web interface and logs. - Listing secret values returns the value as
******. - Secret values belong to one namespace and don't resolve in another.
Two things to plan around:
- An unknown secret value name is left as literal text rather than raising an error, and the S3 request then fails with an authentication error. Verify with the command from Step 3 after creating a reference.
- Secret values aren't encrypted at rest yet. Encryption at rest is planned for 3.1.3.
Scope of the Per-Bucket Keys
Per-bucket keys select which credentials to use for which bucket. They don't restrict which buckets a deployment can reach.
Every Flink pod receives the platform's own object storage credentials in its environment, and the default credential provider chain reads them (see Provide Credentials Using Mounted Files and Provide Credentials Using Kubernetes Secrets above). A bucket with per-bucket keys uses those keys. A bucket without per-bucket keys falls back to the platform account and stays reachable if that account has access to it.
To make the configuration a boundary rather than just a selection:
- Grant the platform's object storage account access only to the buckets the platform itself uses.
- Remove the fallback from the credential provider chain, by adding this key to the same
flinkConfigurationblock used in Step 2 above:
1fs.s3a.aws.credentials.provider: org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProviderWith the provider pinned, only credentials present in the configuration are considered, so every s3a:// destination requires its own keys.