AWS s3 env var for per-tenant S3 key isolation in shared buckets

We run a multi-tenant n8n deployment on AWS EKS — one n8n instance per Kubernetes namespace, each with its own PostgreSQL database, all sharing a single S3 bucket for binary data storage (Enterprise S3 external storage feature).

We use AWS Pod Identity with session-tag ABAC to bind each tenant’s pod to a per-tenant IAM role. The intended IAM resource ARN scoping is:
arn:aws:s3:::our-shared-bucket/${aws:PrincipalTag/kubernetes-namespace}/*

This is the AWS-recommended pattern for per-tenant S3 isolation within a shared bucket. It only works if each tenant’s writes land under a tenant-specific key prefix.

Currently, n8n writes binary data to S3 using this key format:
workflows/{workflowId}/executions/{executionId}/binary_data/{binaryFileId}

Because all keys start with workflows/, the ABAC resource scoping matches none of them, and every PutObject call is denied.

We’d like a new environment variable — for example N8N_EXTERNAL_STORAGE_S3_BUCKET_PREFIX — honoured by the binary-data S3 key builder, so that uploaded keys become:
<prefix>/workflows/<workflowId>/executions/<executionId>/binary_data/<uuid>

This would allow each tenant’s instance to be configured with a unique prefix (e.g. the Kubernetes namespace name), enabling proper ABAC-based isolation within a shared bucket without requiring separate buckets per tenant.

Some facts:

  • Database: PostgreSQL

    Running n8n via: Kubernetes (EKS)

  • Operating system: Linux (EKS nodes)

  • Binary data mode: s3 (N8N_DEFAULT_BINARY_DATA_MODE=s3)

  • License: Enterprise

Any ideas?

@supera74 ur diagnosis is right — theres no env var for binary data S3 key prefix in current n8n core. the available external-storage env vars are HOST, BUCKET_NAME, BUCKET_REGION, ACCESS_KEY, ACCESS_SECRET, and AUTH_AUTO_DETECT. the workflows/{workflowId}/executions/{executionId}/binary_data/{binaryFileId} path is hardcoded in the binary data S3 manager.

immediate workaround if u can tolerate it — go bucket-per-tenant instead of shared bucket. set N8N_EXTERNAL_STORAGE_S3_BUCKET_NAME per pod from the namespace name (tenant-${KUBERNETES_NAMESPACE}-binary or similar). ur ABAC policy becomes:

{
  "Effect": "Allow",
  "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
  "Resource": "arn:aws:s3:::tenant-${aws:PrincipalTag/kubernetes-namespace}-binary/*"
}

trades bucket sprawl for working ABAC. cost-attribution actually gets easier too since per-tenant bucket = per-tenant CloudWatch metrics out of the box, and lifecycle policies / replication rules become per-tenant config instead of prefix-scoped.

for the actual feature u want, file it at GitHub - n8n-io/n8n: Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations. · GitHub with the use case verbatim from ur post — ur scenario (multi-tenant EKS + Pod Identity ABAC + shared bucket) is exactly the production setup the feature would unblock and that framing helps prioritization. since ur on Enterprise u also have CSM access for internal escalation which moves faster than the github queue.

if u absolutely cant move to bucket-per-tenant and need shared-bucket today, the only other working option is wrapping S3 writes through an S3 Object Lambda that rewrites the incoming key to inject the prefix server-side. adds latency to every binary write and is significantly more ops complexity than buckets-per-tenant tho.

for engineering teams that want it now — the binary data S3 client is a ~20-line change to honor a N8N_EXTERNAL_STORAGE_S3_BUCKET_PREFIX env var and prepend it in the key builder. could maintain a fork or submit upstream as a PR with this exact issue as the motivation, n8n usually merges narrow well-justified PRs from Enterprise users.

1 Like

This is a real gap for Enterprise multi-tenant setups on EKS with shared S3. The Pod Identity ABAC approach you’ve described is exactly the AWS-recommended pattern - the problem is n8n hardcoding the key prefix breaks the IAM scoping entirely. A configurable N8N_BINARY_DATA_S3_KEY_PREFIX env var would solve this cleanly. Upvoted - this matters beyond just your setup, anyone running multiple n8n instances with shared S3 and IAM-based isolation hits this.

1 Like

Thanks for the prompt reply.

Quick note on the Object Lambda fallback though. I don’t think it actually works for this use case. Per the AWS docs, S3 Object Lambda only intercepts GET, LIST, and HEAD requests — “all other requests are processed as normal”, so PutObject passes straight through to the supporting access point untouched. There’s no write-side hook to rewrite the incoming key on.

On top of that, there is this:

As of November 7th, 2025, S3 Object Lambda is available only to existing customers that are currently using the service as well as to select AWS Partner Network (APN) partners.

Source: https://docs.aws.amazon.com/AmazonS3/latest/userguide/transforming-objects.html

Thanks for the suggestions. We’ve contacted our CSM.

good catch — ur right that Object Lambda is read-side only (GET/LIST/HEAD), my bad on that one, hopefully your CSM helps you!

2 Likes

This is a real gap for multi-tenant n8n on Kubernetes. The bucket-per-tenant approach works but adds operational overhead (more IAM roles, more bucket configs) compared to what should be a single env var. Worth a GitHub issue/PR - the change to honor N8N_EXTERNAL_STORAGE_S3_KEY_PREFIX sounds small enough to land.

1 Like