Docs Home
Viewing docs for
Self-ManagedNot available for BYOC

Blob Storage

On this page

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:

TEXT
1s3.accessKeyId

The 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:

YAML
1global:
2  blobStorage:
3    credentialsDir: /conf/blob-creds

Replace /conf/blob-creds with the path where your credentials files are mounted inside the container.

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:

BASH
1kubectl create secret generic blob-storage-credentials \
2  --from-literal=s3.accessKeyId=AKIAEXAMPLEACCESSKEYID \
3  --from-literal=s3.secretAccessKey=wJalrXUtnFEMIEXAMPLEKEYsecretkey \
4  --namespace vvp-system

Reference the Secret in Your Values

Set the secret name in your values.yaml:

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.

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:

BASH
1helm upgrade --install <RELEASE_NAME> \
2  oci://registry.ververica.cloud/platform-charts/ververica-platform \
3  --version <VERSION> \
4  --namespace vvp-system \
5  --values values.yaml

Replace <RELEASE_NAME> with your Helm release name and <VERSION> with the installed platform version.

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:

BASH
1kubectl create secret generic vvp-license \
2  --from-file=license.yaml=/path/to/your/license.yaml \
3  --namespace vvp-system

Reference the Secret in Your Values

Set the secret name in your values.yaml:

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:

YAML
1vvp-appagent:
2  sqlService:
3    s3aCompatibility:
4      enabled: true

Set 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:

SQL
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.

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:

YAML
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 S3

Repeat the block for every bucket. For example, two buckets, each with its own credentials:

YAML
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:

SQL
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.key and secret.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:

BASH
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:

BASH
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:

BASH
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:

BASH
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.

Step 3: Verify What Was Stored

BASH
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.

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:

BASH
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.

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:

  1. Grant the platform's object storage account access only to the buckets the platform itself uses.
  2. Remove the fallback from the credential provider chain, by adding this key to the same flinkConfiguration block used in Step 2 above:
TEXT
1fs.s3a.aws.credentials.provider: org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider

With the provider pinned, only credentials present in the configuration are considered, so every s3a:// destination requires its own keys.

Was this helpful?