Describe the problem/error/question
Selective, per-user promotion between two n8n environments — is there a better pattern than the default Git flow?
Hi all,
We’re running two n8n environments (QA and production) on self-hosted EKS, in queue mode, and we’re trying to design a robust promotion process. I’d love to hear how others have solved this at scale, because the default Git-based source control flow doesn’t seem to fit our case.
Our context
We expect hundreds of users with differentiated access across dozens of n8n projects. Users should only be able to promote the objects (workflows, credentials, variables, data tables) they actually have visibility on, based on their project access.
Why the default 2-environment / 2-branch flow breaks down for us
The standard flow is: QA pushes to a development branch, a user opens an MR to main, and after merge, production pulls from main. The problem is that there’s a single shared development branch and push/pull operate at instance level, with no per-object selectivity:
- If two users from different teams push from QA, everything lands together in
development. There’s no way to choose which of the two sets of changes to carry forward tomain— they both go in. - On the pull side, production pulls the whole branch state; you can’t select which objects to import.
So one team’s promotion inevitably drags along another team’s work. With our scale and access model, this makes promotion governance essentially unmanageable.
What we considered building, and why we’re hesitant
We designed a custom promotion workflow: a user selects the specific objects they’re entitled to promote via a form, and the workflow creates an isolated branch + merge request per promotion event (using the GitLab Commits API, so each promotion is atomic and independent, and an admin can approve one MR without affecting another’s work in main). The isolation part works well conceptually.
The blocker is how to obtain the object files in the correct format. We first tried fetching objects from the n8n public API (e.g. GET /workflows/{id}) and committing those. But we found the API response format is not the same as the format n8n writes to the repo during a native push.
We could write custom logic to convert the API format into the repo format, but we really don’t want to because it’s fragile: if a future n8n version changes either the API response or the native push/pull format, our conversion logic breaks, and in the meantime nobody can promote between environments. That’s an unacceptable failure mode for something this central.
The question
Has anyone solved selective, per-user, per-object promotion across environments at this kind of scale? Specifically:
- Is there a supported way to get n8n to serialise objects in the native repo format without reconstructing it ourselves (short of relying on the full instance-level push to a shared branch)?
- Has anyone built a custom promotion layer on top of native source control, and how did you handle the per-object selectivity and format problem?
- Are we missing a feature or configuration that would make per-object / per-project promotion feasible with the native tooling?
Any pointers, patterns, or war stories would be hugely appreciated. Thanks!
Information on your n8n setup
- n8n version: 2.30.6
- Database (default: SQLite): PostgreSQL
- n8n EXECUTIONS_PROCESS setting (default: own, main): queue mode
- Running n8n via (Docker, npm, n8n cloud, desktop app): Docker
- Operating system: Linux