{"slug": "roll-it-out-roll-it-back-never-redeploy", "title": "Roll it out, roll it back, never redeploy", "summary": "Railway introduced feature flags into its platform, allowing developers and AI agents to toggle features on and off without redeploying. The flags are accessible via dashboard, CLI, SDK, and MCP, enabling safe rollouts with targeting rules and instant rollbacks. This changes the shipping rhythm by letting code land dark and release gradually.", "body_md": "# Roll it out, roll it back, never redeploy\n\nThere's now a safer way for your agent to yeet stuff into production on Railway: Feature flags.\n\nIf you haven't used them before, a flag is a value your app checks at runtime to decide which code path runs, which means turning a feature on or off no longer requires a deploy. Code lands in production dark, behind a flag that defaults to off, so the merge changes nothing for anyone.\n\nFrom there, the release happens on your terms: turn the feature on for your team, then 10% of users, then everyone, and if anything looks wrong along the way, turn it back off in seconds instead of scrambling to redeploy.\n\nFeature flags are now wired into the dashboard, the CLI, the SDK, and MCP, so your agents can drive them the same way you do: create a flag, gate a change behind it, and run the rollout with confidence.\n\n## What it looks like\n\nWith the Railway CLI installed, run `railway setup agent --remote`\n\nto set your agent up with two things: the Railway MCP server and Railway's agent skills, which cover the full feature flag workflow and stay updated as the platform evolves. Once that's in place, the shortest path to your first flag is a prompt:\n\nGate the new checkout flow behind a feature flag, off by default, and roll it out to 25% of Pro users.\n\nThe agent knows exactly what that implies. It wraps the new code path in an SDK read, runs `railway flag`\n\ncommands to create the flag with a safe default and attach the targeting rule, and opens a pull request. That PR is safe to merge on sight, because until the rollout rule says otherwise, the flag serves `false`\n\nto everyone.\n\nAnd if you're more of a UI enjoyer, everything the agent just did is sitting in your project, ready to inspect and tweak.\n\nCreating a flag and adding a targeting rule in the dashboard (Settings → Feature Flags)Flags live in your project under **Settings → Feature Flags**. Creating one takes a name, a type (`bool`\n\n, `string`\n\n, `number`\n\n, or `json`\n\n), and a default value, and targeting rules read the way you'd say them out loud: when `plan`\n\nequals `enterprise`\n\n, serve `true`\n\n, or serve `true`\n\nfor 25% of users. The dashboard, the CLI, the SDK, and your agents all read and write the same flags, so what you see in settings is exactly what your app resolves at runtime.\n\n## One rollout, start to finish\n\nThe clearest way to show how this changes your shipping rhythm is to run one release end to end. Say an agent has rewritten your checkout flow, the change is reviewed, and you want it live without betting your whole user base on it.\n\nEvery flag has a default value, plus optional rules that override it for specific users. Start by creating the flag with a safe default. There's no separate create command, `railway flag set `\n\ncreates the flag if it doesn't exist and infers its type from the value:\n\n```\nrailway flag set checkout-v2 false\n```\n\nIn your app, put the new code path behind the flag. Install the `railway`\n\nTypeScript SDK and call `flags.init()`\n\nonce at startup. After that, a read is one call:\n\n``` js\nimport { flags } from \"railway\";\n\nawait flags.init();\n\nif (flags.getBoolean(\"checkout-v2\")) {\n  // new checkout\n}\n```\n\nA plain on/off switch needs nothing more, and most flags are exactly that. When you want targeting, like \"my team only\" or \"25% of users\", pass context about who's asking, plus an optional fallback for when the flag can't be read:\n\n``` js\nconst useNewCheckout = flags.getBoolean(\n  \"checkout-v2\",\n  { key: user.id, email: user.email, plan: user.plan },\n  false,\n);\n```\n\nThe context is just the attributes you choose to pass. A rule like `plan == \"enterprise\"`\n\nworks because you passed `plan`\n\n, not because Railway knows what a plan is. The only special attribute is `key`\n\n, which identifies the user for percentage rollouts.\n\nFlags aren't limited to booleans either. `getString`\n\n, `getNumber`\n\n, and `getJson`\n\nwork the same way, so you can change things like rate limits or model config at runtime without redeploying.\n\nNow merge. The default is `false`\n\n, so nothing changes for your users yet. Then the release starts, beginning with your own team:\n\n```\nrailway flag set checkout-v2 true --rule-id team --when 'email contains \"@yourco.com\"'\n```\n\nOnce checkout looks healthy for the people who built it, widen to a slice of real users with a percentage rollout. That's what `bucket()`\n\ndoes in a rule: it spreads your users evenly across a range from 0 to 1, based on the `key`\n\nyour app passes in the context (the user's ID, in the snippet above). So `bucket(key) < 0.25`\n\nmeans \"the quarter of my users who land in the first 25% of the range\":\n\n```\nrailway flag set checkout-v2 true --rule-id rollout-25 --when 'bucket(key) < 0.25'\n```\n\nA user's spot in that range never changes. Raise the threshold from 10% to 25% and everyone who already had the new checkout keeps it, new users join, and nobody flips back and forth while you adjust.\n\nConditions combine with `&&`\n\nand `||`\n\ntoo, so \"Pro customers, but only a quarter of them\" is a single rule: `--when 'plan == \"pro\" && bucket(key) < 0.25'`\n\n(the [docs](https://docs.railway.com/cli/flag) cover the full expression syntax).\n\nThe expressions are also exactly the kind of thing your agent is good at. If you'd rather stay in business terms, ask for the outcome instead:\n\nTurn checkout-v2 on for 25% of Pro users, and keep it on for the whole team.\n\nAn agent set up with the Railway MCP server and agent skills (the `railway setup agent --remote`\n\ncombo from earlier) translates that into the rules you just saw, and `railway flag list`\n\nshows you what it wrote before any user sees it.\n\nIf a rule turns out to be wrong, remove it with` railway flag unset checkout-v2 --rule-id rollout-25`\n\nand everyone it covered goes back to the default, no deploy involved. And once the flag has sat at 100% for a while, delete it and make the new code path permanent:\n\n```\nrailway flag delete checkout-v2\n```\n\nThroughout all of this, reads never make a network call. `init()`\n\nfetches your project's rulesets once, and from then on every read evaluates against an in-memory copy that the SDK keeps fresh in the background. A read costs about as much as a function call, so you can put one in a hot path without budgeting for it. Flags end up behaving like environment variables that update without a redeploy.\n\nAnd when you're staring at a value wondering how it got there, swap the getter for its `evaluate`\n\nvariant. Here's what it returns for a user who isn't on your team but landed inside the 25% rollout:\n\n``` js\nconst evaluation = flags.evaluateBoolean(\n  \"checkout-v2\",\n  { key: user.id, email: user.email, plan: user.plan },\n  false,\n);\n\n// {\n//   value: true,\n//   reason: \"SPLIT\",\n//   trace: [\n//     { ruleId: \"team\", matched: false, value: null },\n//     { ruleId: \"rollout-25\", matched: true, value: true },\n//   ],\n// }\n```\n\nThe team rule didn't match, the rollout did, and `\"SPLIT\"`\n\nmeans a percentage rollout made the call. Resolution is a pure function with no hidden state, so \"why does this user see this value\" has an exact, reproducible answer.\n\n## When rules disagree, production wins\n\nMost flag systems resolve overlapping rules with priorities: rule three beats rule seven, or whichever rule was written last wins. In our experience, that's how you end up serving a value nobody chose, and debugging it means reconstructing who wrote what in which order.\n\nRailway instead evaluates every matching rule at read time. When the matching rules agree, you get that value, and when they disagree, or nothing matches, you get the flag's default. There are no priorities to reason about, which means there is nothing to mis-order.\n\nThat makes the default the most important value on the flag. It's what production looks like, and every rule is a temporary exception on top of it. Whatever goes wrong, the result is the same:\n\n-\nTwo rules conflict for the same user? They see the default.\n\n-\nA rollout rule needs a\n\n`key`\n\nand the context doesn't have one? The default, never a random assignment. -\nYour app can't reach Railway at all? The fallback you wrote in code.\n\nThe fallback in that last case is different from the default: it only applies when the flag can't be read at all. When the flag exists and no rule matches, you get the default you configured. Either way, the worst case of any conflict, outage, or typo is that a user sees plain old production.\n\n## Where this goes\n\nFeature flags in this first version resolve to literal values, but the primitive underneath (a context resolving to a value at read time) extends well past config. There's a lot a rule could decide beyond a boolean, and we're exploring where to take it next.\n\nFeature flags are available on every project today. Create one under **Settings → Feature Flags**, read the [feature flags docs](https://docs.railway.com/feature-flags), and tell us what you build with them (or what's missing) on [Central Station](https://station.railway.com/new?type=feedback).", "url": "https://wpnews.pro/news/roll-it-out-roll-it-back-never-redeploy", "canonical_source": "https://blog.railway.com/p/feature-flags", "published_at": "2026-07-16 18:30:33.499827+00:00", "updated_at": "2026-07-16 18:30:35.671476+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["Railway"], "alternates": {"html": "https://wpnews.pro/news/roll-it-out-roll-it-back-never-redeploy", "markdown": "https://wpnews.pro/news/roll-it-out-roll-it-back-never-redeploy.md", "text": "https://wpnews.pro/news/roll-it-out-roll-it-back-never-redeploy.txt", "jsonld": "https://wpnews.pro/news/roll-it-out-roll-it-back-never-redeploy.jsonld"}}