Module 4: Dev, Staging, and Prod Environments in Self-Hosted
1. Introduction: what a real environment is
Description
By the end of this lesson you will be able to explain what an environment is in the serious sense of the word, precisely tell apart what changes between dev, staging, and prod —and above all, what doesn't change— and recognize why "everything in one instance" is an antipattern that sooner or later breaks production. You'll have the full map of this module's eight steps and you'll understand why, since n8n's Community edition doesn't ship the "environments" feature natively, we're going to build that isolation ourselves with Docker Compose, at zero cost.
This matters because it's the line where an automator stops being a lucky amateur and becomes someone a company can trust with its operation. A workflow builder tests their changes against the real system and prays nothing breaks. A system owner has a safe place to get things wrong —dev—, a place to rehearse under near-real conditions —staging—, and only then touches what actually matters —prod. That separation is exactly what serious job postings ask for when they write "experience with staging and production environments." It isn't a big-company luxury: it's the difference between a quiet night and a sleepless one.
Connection to the module: this lesson is the map, not yet the technique. Here you set up the problem (why one instance isn't enough) and the vocabulary you're going to use across the eight steps. Lesson 2 gives you the reproducible base —the Self-Hosted AI Starter Kit— and explains what a container is and what Docker Compose is. Lesson 3 turns that base into three isolated stacks. Lesson 4 gives each one its own .env and its own encryption key. Lessons 5 and 6 handle credentials and secrets per environment. Lesson 7 is the honest decision about when the native feature is worth paying for. And lesson 8 is the project: the three environments running, with proof they're truly isolated. A continuity note: in Module 3 you already got credentials out of the repository and met N8N_ENCRYPTION_KEY; this module picks up that thread and carries it to "one key and one set of credentials per environment."
From one scenario to three: the theater and the rehearsal
Think of a theater company staging a play. There's one play: the same script, the same characters, the same scenes. But that play gets performed in three very different situations before and during its life.
First there's the rehearsal in the practice room. There, the actors get things wrong on purpose: they try out a tone, skip a scene, repeat a line ten times. If someone stumbles, nothing happens: no audience, no tickets sold, no critic in the front row. The practice room exists to get things wrong.
Then there's the dress rehearsal, with costumes, lights, and the real stage, but still no paying audience. It's as close to the real show as you can get without risking anything. If something fails at the dress rehearsal —a light that doesn't come on in time, a scene change that snags— it gets fixed that night and nobody finds out. It's the last filter before what counts.
And finally there's opening night, with a full house, tickets sold, and the critic taking notes. Here a mistake is seen, paid for, and remembered. Nobody rehearses at the opening. You arrive at the opening with the play already tested in the two previous stages.
Those three moments are exactly dev, staging, and prod:
| Environment | What it is in theater | What it's for | What happens if something fails |
|---|---|---|---|
dev | The practice room | Building and breaking without fear | Nothing: nobody's watching |
staging | The dress rehearsal | Rehearsing under near-real conditions | Fixed that night, no audience |
prod | Opening night | Running for real, with what matters | Seen, paid for, remembered |
Notice the most important part of the analogy, because it's the idea holding up the whole module: the play is the same across the three stages. The script doesn't change between the practice room and opening night. What changes is the context: the audience, the lights, the consequences. In our world, the "play" is the order-triage workflow, and it's identical in dev, staging, and prod. What changes between environments isn't the logic; it's what data it runs with, what credentials it connects with, and what URLs it responds on.
This is confusion number one among people who never worked with environments: they think having three environments means having three versions of the workflow. No. It's one workflow —one play— performed on three different stages. I'm nailing this down now because half of this module's mistakes come from here.
What changes, exactly, between environments (and what doesn't)
Let's be precise, because "an environment" sounds abstract until you break it into its pieces. An environment is the complete set of conditions your workflow runs under: the n8n instance running it, the database it lives in, the credentials it uses, the data it processes, and the URLs it's reached through. Two environments are "different" when those conditions differ even though the workflow is the same.
Between dev, staging, and prod, four things change, and it's worth naming them one by one:
1. The data. In dev you process made-up data: fake orders you built for testing. In staging, data that resembles the real thing but still has no consequences. In prod, the real orders from the cafés that buy from Cumbre. A test order that gets wrongly approved in dev doesn't cost anyone a cent; the same mistake in prod ships coffee nobody ordered.
2. The credentials. In dev and staging you use test accounts and sandbox keys: a toy CRM, an AI model key with a low spending limit. In prod you use the real credentials: the CRM that really holds Cumbre's 400 customers' data. This is all of lesson 5. For now, hold onto the fact that real keys only live in prod.
3. The URLs and webhooks. Each n8n instance runs at a different address. dev on one port, staging on another, prod on another. And since order-triage starts with a Webhook, the URL that triggers that workflow is different in each environment. A test order comes in through dev's URL; a real order comes in through prod's. If those URLs got mixed up, a real order could land in the test environment, or the other way around. This is lesson 4.
4. The encryption key. Each environment has its own N8N_ENCRYPTION_KEY —the master key n8n uses to encrypt credentials. You already met it in Module 3. Here the key idea is that dev's and prod's are different on purpose, so a secret encrypted in one environment can't be decrypted in another. It's a wall between environments. Lesson 4.
And what doesn't change? One thing, but it's the most important: the workflow's logic. order-triage's JSON —which nodes it has, how they're connected, what the AI Agent decides, which endpoint the HTTP Request calls— is identical across the three environments. That's why, as you saw in Module 3, the workflow gets versioned once in cumbre-automations/workflows/, not one copy per environment. What differs lives separately, in each environment's configuration.
This is the line that organizes the whole module, and it's worth stating as a rule:
What's the same everywhere (the logic) gets versioned once. What changes depending on where it runs (data, credentials, URLs, key) lives in each environment's configuration.
Separating "what's shared" from "what varies" isn't an n8n idea nor one from this guide. It's the principle that organizes any system deployed in more than one place, from a mobile app to a satellite. If you internalize it here, you're going to recognize it everywhere for the rest of your career.
Worked example: the same change, with and without environments
Let's look at the difference with a concrete Cumbre case, without touching any tool yet.
Cumbre's team wants to change order-triage: orders over 5000 pesos should go to manual review instead of getting auto-approved. It's a small change to the workflow. Let's look at how that change happens in the two worlds.
World 1: one instance. Cumbre has a single n8n, the one processing real orders. Right there, someone edits order-triage, adds the threshold condition, and saves. The instant they save, the change is in production. If the condition had a bug —say they typed "greater than 500" instead of "5000"— from that second on, every real order over 500 pesos starts going to manual review. The support team gets flooded. Nobody tested it because there was nowhere to test it: the only n8n that exists is the one serving real customers. The test was the accident.
World 2: three environments. Cumbre has dev, staging, and prod. The same change is made first in dev, against made-up orders and a toy CRM. There, the threshold bug is caught —"500" instead of "5000"— because a test order for 600 pesos goes to review when it shouldn't. It gets fixed, tested again in dev, rehearsed in staging with data resembling the real thing, and only when the behavior is correct does it get promoted to prod. The bug existed, but it died in dev, where it cost nobody anything.
What to expect. The workflow change is identical in both worlds: the same condition, the same node. What changes isn't what gets done, it's where the bug gets discovered. In World 1, the bug gets discovered in production, with real customers as collateral damage. In World 2, it gets discovered in dev, in a padded room where getting it wrong is free. Environments don't stop you from making mistakes —nothing stops that— they make your mistakes cheap.
That's the whole promise of this module in one sentence: we're building you the padded room.
Why three environments, and not two (or five)
A reasonable question at this point: if dev is where you build and prod is where it really runs, what's the middle one, staging, for? Wouldn't two be enough? It's worth answering, because staging is the environment most people misunderstand, and skipping it is a common mistake.
staging exists to close a concrete gap. dev is your playroom: there you experiment, leave things half-done, have made-up data and maybe a toy CRM's credentials that don't fully resemble the real one. prod is the real thing, with all its conditions. Between those two there's a distance, and hiding in that distance are the bugs that "work in dev but fail in prod": real data shaped in a way your made-up data never was, an order volume your test of three never revealed, a subtle difference between the sandbox CRM and the real one.
staging is the dress rehearsal that closes that gap. It's an environment as close to prod as possible —same services, data resembling the real thing, test credentials but pointing at systems that mimic the real production ones— but still without consequences. Its job is that, if a change passes through staging without problems, you have high confidence it'll pass in prod. It's the last filter before what matters.
Think of it with a simple rule: dev is for making the change work; staging is for trusting it's going to work in production. They're two different questions. Your playroom answers the first; the second needs an environment that genuinely resembles production.
And why not five, or seven? Because each environment costs something —memory, maintenance, the discipline to keep them alike— and three is the point where the benefit stops growing for most teams: one to break, one to rehearse, one for the real thing. Very large teams sometimes add more (a performance-testing environment, one per team), but for Cumbre —and for almost everyone— three is the right number. Don't over-build: three well-isolated environments are worth more than seven half-maintained ones.
That said, an honest practical note: if you're learning and your machine is tight on memory, starting with two well-built environments (dev and prod) and adding staging later is a reasonable concession. What isn't negotiable is having at least the separation between "where I test" and "the real thing." The third one improves confidence; the first two prevent disaster.
Why "everything in one instance" is an antipattern
An antipattern is a solution that seems reasonable, gets used a lot, and systematically ends badly. Having a single n8n instance for everything is the most common antipattern among people newly professionalizing their automations, and it's worth understanding why it fails, not just that it fails.
The temptation is obvious: one instance is simpler. One n8n to bring up, one URL to remember, one database. Why complicate things with three? The answer is that a single instance's simplicity is real the day everything works, and it's a trap the day you need to change something. The concrete problems:
There's nowhere to test without risk. You already saw it in the example: if the only n8n is the production one, every change gets tested in production. There's no safe place to get it wrong. This isn't theoretical: it's the number-one cause of incidents in small automations.
Test data contaminates real data. If you test order-triage on the same instance processing real orders, your test orders enter the same database, mix with the real ones, trigger the same notifications. You send a test email to a real customer. You register a made-up order in the real CRM. Cleaning that up afterward is a nightmare, when it's even possible.
A test secret and a real one dangerously coexist. With a single instance, either you use the real credential for everything —and then every test touches the real CRM— or you keep swapping the credential by hand every time you want to test, which is slow and prone to catastrophic forgetfulness ("I forgot to put the real key back and production's been down for two hours").
A half-finished change breaks what was working. On a single instance you can't have "the stable version running while I test the new one." The instance is in one state at a time. The moment you start editing, the stable version isn't running intact anymore: it's half-done.
The correct pattern —the one this module builds— is isolating. Three separate instances, each in its own state, with its own data and secrets, that don't touch each other. The cost is a bit more setup up front. The payoff is you never test against production again. That trade —a little more complexity today in exchange for never breaking production— is one of the best you're going to make as an automator.
Community doesn't ship "environments," and that's why we build it
Here comes the plan-honesty part running through the whole guide. n8n has a feature called environments that does exactly this: it lets you have development, staging, and production instances connected and lets you move work between them from the interface, backed by Git. It's a very good feature.
And it's paid. According to the official docs, the native environments and integrated version-control feature lives in the Business and Enterprise plans (plans change names and content over time, so it's always worth confirming this on n8n's pricing page at the moment you read it). The Community edition —the free, open-source one you install on your own machine— does not include it.
That might sound like bad news. It isn't, and here's the point that makes this guide valuable: isolation between environments isn't n8n magic; it's a property the infrastructure underneath provides. The native feature gives it to you packaged and convenient, with buttons in the interface. But the same thing is achieved —real isolation, not an imitation— with the tool that runs n8n underneath: Docker Compose. And Docker Compose is free.
What we're going to build in this module is exactly that: three n8n Community instances, isolated with Docker Compose, each with its own database, its own encryption key, its own credentials, and its own URLs. Without paying a cent. The native feature saves you setup work and gives you interface convenience; the self-hosted pattern gives you the same isolation in exchange for a bit more manual work. Lesson 7 puts both options side by side with an honest decision matrix so you know when it's worth paying. For now, the news is simple and good: everything this module teaches is done for free on Community.
A change, start to finish, across the three environments
To keep the three environments from staying an abstraction, let's follow a concrete change on its full journey. You're not going to do this yet —it's the module's destination, and even Module 6's— but seeing it whole gives you the north star for why we build each piece.
Cumbre's team wants order-triage to send orders over 5000 pesos to manual review. The change travels like this:
-
It's born in
dev. Someone editsorder-triageon thedevinstance (http://localhost:5678), adds the threshold condition, and tests it against made-up orders and a toy CRM. They find and fix bugs here, where nothing costs anything. Once it works, they export the workflow and version it incumbre-automationswith Git (Modules 2 and 3). -
It's rehearsed in
staging. The same versionedorder-triage.jsongets imported intostaging(http://localhost:5679), which resembles production: data like the real thing, test credentials pointing at systems that resemble the real ones. It runs there and the behavior gets confirmed correct under near-real conditions. This is the filter that catches the bugsdevdidn't reveal. -
It reaches
prod. Only once it's passedstaging, the same workflow gets imported intoprod(http://localhost:5680), which runs with Cumbre's real orders and the CRM's real credentials. The change arriving here has already been tested twice; it arrives with confidence, not hope. -
If something goes wrong, it's rolled back. And if despite everything the change breaks something in
prod, since the workflow is versioned, it goes back to the previous version that worked (Module 6's rollback). The final safety net.
Notice what makes each step possible, because it's this guide's syllabus. Versioning with Git (Modules 2 and 3) is what lets "the same workflow" travel between environments as a controlled artifact. Environment isolation (this module) is what makes dev safe to break and prod untouchable while you test. Sandbox testing (Module 5) is what happens inside dev and staging. And promotion and rollback (Module 6) are steps 2, 3, and 4. The whole path you just read is what separates a system owner from a builder: they don't build the change differently, they deliver it differently.
This module, 4, builds the stage where that journey happens: the three isolated environments. Without them, the change is born and dies on the same instance —the antipattern— and no journey is possible.
This module's map
These are the eight steps, and the order isn't arbitrary:
| Lesson | What it solves |
|---|---|
| 2 | The reproducible base: the Self-Hosted AI Starter Kit, what a container is, what Docker Compose is |
| 3 | Defining dev, staging, and prod as isolated Docker Compose stacks; what never gets shared |
| 4 | One .env per environment, a different encryption key per environment, and each one's own URLs |
| 5 | Credentials per environment: test in dev/staging, real only in prod |
| 6 | External secrets and variables: $env, n8n Variables, and what's Enterprise with its Community alternative |
| 7 | The native environments feature (paid): honest decision matrix on when it's worth paying |
| 8 | Project: the three environments running, and proof they're isolated |
First the base (2): without understanding what a container is and what Docker Compose is, the rest doesn't hold up. Then the structure: three stacks (3), their configuration (4), their credentials (5), and their secrets (6). Then the economic truth (7): what's free and what costs money. And the project (8) joins it all into something running that you can demonstrate.
A boundary note before moving on. This module brings up local environments, on your own machine, with Docker Compose. We're not going to deploy anything on the internet, or set up a server, or a reverse proxy, or harden Linux. That's real production infrastructure, and it's another guide's topic. Here "prod" is a local environment that represents production so you learn the isolation pattern; it isn't a server exposed to the world. When the day comes to actually deploy, the pattern you learn here is the same; only where it runs changes.
Common mistakes
Believing three environments mean three versions of the workflow (conceptual). What happens: someone understands "dev, staging, prod" as "three copies of order-triage, one per environment," and starts maintaining three JSON files. Before long they fix a bug in one and forget it in the other two, and the environments start to diverge. Why it happens: the word "environment" suggests "separate place," and from there it's a short jump to "separate thing." How to spot it: if you have order-triage-dev.json, order-triage-staging.json, and order-triage-prod.json, you've already fallen for it. How to fix it: one single versioned order-triage.json, running across the three environments. What changes between environments isn't the workflow, it's its configuration —data, credentials, URLs— and that lives separately. One workflow, many environments.
Testing on the production instance "carefully" (practical and dangerous). What happens: someone knows they shouldn't, but they only have one instance and are "just going to test one quick thing" in production. The quick thing sends a real email, or corrupts real data, or takes the workflow down for ten minutes. Why it happens: setting up separate environments feels like work, and "carefully" feels like enough. How to spot it: if your plan for testing a change includes the phrase "in production but carefully," you have the problem. How to fix it: it's exactly what this module builds. There's no such thing as "carefully" in production: there's the test environment, or there's the accident. This module gives you the test environment so you never depend on "carefully."
Thinking environments are impossible without the Enterprise feature (conceptual). What happens: someone reads that "environments" is paid, concludes there's no way to have separate environments in Community, and resigns themselves to a single instance. Why it happens: it's easy to confuse "the feature by that name is paid" with "the capability is paid." How to spot it: if you believe you need Enterprise to separate dev from prod, you have this belief. How to fix it: the native feature is a convenience, not the only path. Real isolation comes from Docker Compose, for free. What you pay for in Enterprise is the convenient interface and support, not the possibility itself. This whole module is proof it's done for free.
Exercises
Exercise 1 — Classify what changes and what doesn't. For each of these six elements of order-triage, say whether it's something that changes between dev and prod, or something that stays the same in both, and why in one sentence: (a) the AI Agent node that classifies the order; (b) the CRM's API key; (c) the order in which the nodes are connected; (d) the webhook URL that triggers the workflow; (e) the logic of "orders over 5000 go to review"; (f) the order data being processed.
See solution
(a) Stays the same. The AI Agent is part of the workflow's logic; the same node, configured the same way, runs across the three environments. (What changes is the credential the model uses, not the node.)
(b) Changes. In dev it's a sandbox key with limited spend or a toy CRM; in prod it's the real CRM's key. The secret's value differs per environment.
(c) Stays the same. Connections between nodes are pure logic; identical across the three environments.
(d) Changes. Each instance runs at a different address, so the webhook URL is different in each environment.
(e) Stays the same. The 5000 threshold and the decision to send to review are workflow logic; the same across the three environments.
(f) Changes. In dev it's made-up orders; in prod, Cumbre's cafés' real orders.
Why it works: if you got all six right, you've already internalized the line that governs the module. The logic (a, c, e) is shared and gets versioned once; the context (b, d, f) changes per environment and lives in configuration. The most confusing one is (a): "doesn't the AI Agent change?" The node doesn't; its credential does. Separating the node from its credential is exactly what makes a workflow-for-many-environments possible.
Exercise 2 — The single-instance accident. Write, in four or five sentences, a realistic story of something going wrong at Cumbre from having a single n8n instance for everything. Include: what change was being attempted, what went wrong, and what concrete damage it caused from not having a test environment. Then answer: at exactly what moment would three environments have cut the problem off?
See solution
There's no single correct story, but a typical one: Cumbre's team wanted order-triage to send a confirmation email to the customer when an order gets approved. They set up the email node on the only instance —the production one— with a template that, by mistake, had an empty subject line and body text that said "TEST TEST." On save, the workflow was already active in production, so the next real order triggered an email with an empty subject and "TEST TEST" as the body to a real customer, who called in confused. The damage: a real customer received garbage and lost trust.
Three environments would have cut the problem off at the very first moment: the test email would have gone to a test address in dev, where "TEST TEST" is exactly what you expect to see, and the template bug would have been discovered there, before any real customer received anything.
Why it works: the exercise forces you to connect the abstraction ("separate environments") with concrete, human damage (a confused customer). That connection is what makes the pattern stick: you don't set up environments out of abstract discipline, you set them up so "TEST TEST" never reaches a real customer.
Exercise 3 — Rebuild the map. Without looking back at the table, write from memory what each of the seven following lessons (2 through 8) solves, one sentence each. Then compare and mark the ones you missed.
See solution
(2) The reproducible base: the Starter Kit, what a container is, what Docker Compose is. (3) Defining dev/staging/prod as isolated stacks and what never gets shared. (4) One .env per environment, one encryption key per environment, and each one's own URLs. (5) Credentials per environment: test in dev/staging, real only in prod. (6) External secrets and variables: $env, n8n Variables, what's Enterprise and its Community alternative. (7) The native environments feature (paid): decision matrix on when to pay. (8) Project: the three environments running and proof of isolation.
Why it works: if you rebuilt at least five of the seven, you've already internalized the progression —base, structure, configuration, economics, project. The ones people miss most are usually 4 and 6, the most technical ones you haven't seen yet. That's fine: that's what they're for.
Summary and next step
In this lesson you saw what a real environment is: the complete set of conditions your workflow runs under —instance, database, credentials, data, URLs— and what distinguishes dev, staging, and prod. You used the theater image —practice room, dress rehearsal, opening night— to lock in the idea holding up the whole module: the play is the same; what changes is the stage. You named the four things that change between environments (data, credentials, URLs/webhooks, encryption key) and the one thing that doesn't (the workflow's logic), and from there drew the rule: what's shared gets versioned once, what varies lives in each environment's configuration. You understood why "everything in one instance" is an antipattern that tests against production and contaminates real data, and why the correct pattern is isolating. And you saw the plan truth: the native environments feature is paid, but real isolation gets built for free with Docker Compose, which is exactly what this module does.
Before moving on you should be able to: explain in one sentence why a workflow's logic doesn't change between environments but its credentials do; name the four things that change per environment; and say why testing on the production instance "carefully" isn't a strategy.
Lesson 2 brings the theory down to the first concrete tool. You're going to meet the Self-Hosted AI Starter Kit, n8n's official package that bundles n8n, a database, a vector store, and local AI models, all ready to bring up with one command. And before touching it, you're going to finally understand what a container is and what Docker Compose is, explained with plain analogies, because they're the two pieces everything that follows rests on.
Resources
- Source control and environments — n8n Docs — the official section on version control and environments; useful for previewing the native feature only the paid plans ship, lesson 7's topic.
- Environments in n8n — n8n Docs — how n8n describes its native
development/staging/productionmodel connected via Git. - Compare editions — n8n Docs — what the free Community edition ships and what's reserved for paid plans; confirm here, since plans change.
- Self-hosted AI Starter Kit — GitHub — the official repository for the reproducible base you're going to use starting in lesson 2.