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)
@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:
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.
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.
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.
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.
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.