Releases: evolve-platform/evolve-deploy
Releases · evolve-platform/evolve-deploy
Release list
v0.8.0
v0.8.0 - 2026-08-24
Added
- A release has three addresses and there are now three names for them, told apart by what re-points each one.
{{url_revision "site"}}names one revision and is never re-pointed;{{url_stage "site"}}names the label, so it is the side this release staged and what a gate should test;{{url_public "site"}}— spelled{{url}}for short — follows the traffic weights and is what survives the release. The public one is read while planning from what Terraform declared, an ingress hostname or a service URI, at no extra API call, and it is what a hook registering a federated subgraph or a webhook actually wants: those write an address down for something else to read later, and later is after the labels have swapped again.url_stageandurl_revisionare refused in a hook for that reason, andurl_revisionis refused on Cloud Run and ECS because a revision has no address of its own there — a refusal namingurl_stage, not a quiet fallback to it. A target with no address at all is named in the refusal rather than resolving to an empty string.
Changed
{{url "svc"}}now means the address that survives the release rather than the staged side, and insidestrategy.smokeit is refused rather than quietly re-pointed. That refusal is the whole point: resolved silently, every existing smoke block would keep rendering, keep passing, and start testing the version the release is replacing — green, with nothing behind it. A smoke block now has to say which address it means, and the refusal offers both:{{url_stage "svc"}}for the side this release staged,{{url_public "svc"}}for the one still serving the old version. It fails at plan time, so nothing deploys before the config is corrected, and outside a smoke block nothing changes:urlnever worked in a hook at all.
v0.7.0
v0.7.0 - 2026-08-23
Changed
- A failed blue-green release now reads as one release rather than as a row in a table of services, and it ends on what is serving. It used to open with "1 service(s) failed" above a list of three container apps — the staged side is one unit of work, so the count was right and useless — and it never said the one thing worth knowing first: that no traffic moved and the previous version is still up. The failure now names the phase it died in (
the release failed while staging blue after 10m38s), lists a line per target, and closes withNo traffic moved: green still serves the version it served before, and the blue revisions were switched off— or, when the cleanup itself failed, says that instead rather than offering a reassurance the list below it contradicts. A switch that fails says the traffic is back on the side it came from, and an after hook that fails says the deploy worked and nothing was rolled back, because that is the opposite situation and used to look identical. The cleanup also prints as it goes: abandoning a side was silent, so the answer to "is the staged side still running and still being charged for" was a trip to the portal. On Container Apps the line itself is shorter and says more. The app name was in it three times and the platform's own wording buried the fact —is Unhealthy — main is Waiting (Container is waiting with reason: CrashLoopBackOff on legion.), 5 restart(s)is nowrevision evolve-tst-discover--0000039 never started (container main: CrashLoopBackOff, restarted 5 times), followed by theaz containerapp logs showcommand for that revision and that container, since whatever the process printed before it exited is the next thing to look at and it is not in the deploy log. bake_timeis now refused on Container Apps and Cloud Run instead of being read by nobody. It is the window before ECS terminates the old side, and only the ECS driver ever looked at it — set on either of the other two it parsed, validated and then did nothing, which is the one thing a capability is not allowed to do here: a rollback window someone wrote down and did not get is exactly the outcome they were trying to rule out. Both refusals name the setting that answers the same question on that platform —keep_warmon Container Apps, and on Cloud Run nothing, because a revision that keeps its tag at zero traffic is already the window and stays available until the next release takes it.
Fixed
- A Container App revision whose container dies on start now fails the release in about a minute instead of running the wait out to its ten-minute deadline. Container Apps leaves such a revision in
Processing— it is still restarting the container, so by its own account nothing has gone wrong yet — and the wait believed it, because the fact that settles the question lives a level down: the replica's restart count. That count was only ever read once the revision had already admitted to being Degraded or Unhealthy, which for an image that exits immediately can take the better part of the deadline it was supposed to pre-empt. The replicas are now read on every poll while a revision is not yet serving, and a container past its third restart ends the wait whatever the revision says about itself — with the same message as before, naming the container and the command that shows what it printed. A container that is merely slow is unaffected: it has not died, so there is nothing to report and the strike count stays where it was.
v0.6.0
v0.6.0 - 2026-08-23
Added
- A hook entry can now be a named action instead of a command line.
uses: honeycombmarks the deploy on a dataset,uses: sentryregisters the release and the deploy of it, anduses: httpasks for one url and says whether the answer was the expected one. A plain string still means what it always did andcmd:is the same thing written out, so nothing in an existing file has to change. The three exist because they were never really commands: a Honeycomb marker written as curl is six lines of flags, a hand-built JSON body, a header out of an environment variable and a|| echoon the end so that a failed annotation does not fail the release — and every value in it is something the tool already knows. An action is given those, and it can refuse while there is still a plan to refuse: a marker whose API key is nowhere in the environment fails the plan, naming the variable, rather than turning up as a 401 from anafterhook on a release that already succeeded. A marker that fails for any other reason is reported and forgiven, which is what the|| echowas doing.uses: httpretries on a refused connection as much as on a bad status, because a side that has staged is not always answering the instant staging returns — and it cannot be written without the equivalent of--fail, which is how a 500 used to walk through a gate.strategy.smoketakes the same three forms. The plan's dry run now renders a blue-green service's hooks with the side variables it will actually be given:{{.label}}failed to render there and printed nothing at all, which read as a service with no hooks. - A documentation site at https://deploy.evolve-platform.com, built from
docs/and published on every push to main. The README had grown to eight hundred and forty lines covering install, config, references, hooks, blue-green across three clouds and what Terraform has to declare — all of it worth keeping and none of it findable, because a reader who wants to know what${secret:}means on Azure should not have to scroll past the ECS section to find out. The same material is now a site with search and a sidebar, split so that each page answers one question, plus the pages a README has no room for: a first deploy walked through end to end, GitHub Actions with thesetup-evolve-deployaction and cloud OIDC, copyable workflow recipes for drift detection and a rollback button, and a per-cloud table for the blue-green differences that decide which platform is safe to be brave on. The README is now eighty-five lines — what the tool is, how to install it, how to work on it, and where the rest went — which also retires its link to a spec that was never committed. Internal links on the site are relative and checked against the built output on every pull request, anchors included, so a page that moves cannot leave dead links behind it.
Changed
- A hook that succeeds no longer prints what it printed. Three CLIs per service with a screenful each is not the answer to what was deployed, and it buried the handful of lines that are; a hook that fails still prints everything, because that output is the diagnosis.
--verbosestreams it live as before, and now also times every step — each ARM call, each hook and each staged revision — so a release that feels slow can be read rather than guessed at. - Staging hangs the idle label on the new revision while it waits for that revision to run, instead of afterwards. Neither needs the other — the label is written at weight zero and the smoke test that uses its URL does not run until staging has returned — so doing them in sequence spent a whole Container Apps update after the container was already up. Staging now costs the slower of the two rather than the sum.
- The deploy config is now the whole environment of a target rather than a layer over it, so a variable it does not name is removed. It had to be one or the other: on Azure a container's
envis on Terraform'signore_changes, so Terraform writes one at create and can never correct it, and a tool that only laid its own variables on top could not remove one either — a variable set once outlived every release and went on outranking whatever was meant to replace it, with nothing in the system able to say what the environment was. Container Apps, Container App jobs, Cloud Run and ECS all read it the same way, though only Azure forced it: a Cloud Run service is Terraform's to correct, and an ECS base task definition is registered whole so a variable dropped there did reach the next release. They follow anyway, because a list of variables that means the environment on one cloud and a patch over an unseen one on another is not something a reader of a deploy file can be asked to keep track of. On ECS this also retires the bookkeeping that kept a name out ofenvironmentonce it moved tosecrets: both lists are now built from the one declaration and cannot disagree. A config that declares no environment at all still changes nothing, so an image-only deploy is unaffected, and a sidecar's environment stays Terraform's.--allow-env-removalis gone with it: a removal is now something written in the config and read in a diff, not a sign that Terraform quietly stopped declaring something, so interrupting a release for it bought nothing. Removals still appear indiffand in the plan. Configuration that used to reach a service by being left on the resource belongs in App Configuration, Parameter Store or Secret Manager, which the service reads for itself.
v0.5.0
v0.5.0 - 2026-08-22
Changed
envandenvFromare laid over the environment a target already carries rather than replacing it. Terraform declares the environment and the deploy config refines it, so listing one variable sets one variable instead of deleting every other one — which also removes the need for a mode: a config that sets nothing merges nothing, and that is the image-only deploy. The order is what Terraform declares, thenenvFrom, thenenv, thenstrategy.envfor the side being staged, thenEVOLVE_DEPLOY_SIDE. The trade is that the config can no longer remove a variable; removal belongs where the declaration is.--allow-env-removalstays, because a variable can still disappear when Terraform stops declaring one a running target has, and the release that carries that through is worth confirming — but it can no longer fire because someone listed one variable and meant to keep thirty.
Fixed
- A blue-green revision is staged from the container app's own template rather than from the serving revision's, so a probe, a cpu change or an environment variable Terraform declares reaches the next release instead of being staged over every time — previously such a change created a revision with no traffic and was reverted rather than delayed. The serving revision is still where the running version and the environment diff are read from, because with two live revisions the app's template is whichever was created last, which after a failed gate is the attempt that was abandoned; comparing against that would report the retry as already up to date.
v0.4.0
v0.4.0 - 2026-08-22
Added
- Blue-green deployments for Azure Container Apps, GCP Cloud Run and AWS ECS.
strategy: blue-greenstages a release on the side that carries no traffic, runs thesmokecommands against it and switches in one write. The previous version keeps its label at 0% and that is the rollback target, but it is switched off once the new one serves: a Container Apps revision that is not deactivated holds on to its own scale rules, and there is no call that lowers the minReplicas of a running revision, so a side nobody uses would otherwise go on costing money between releases.strategy.keep_warm: trueleaves it running instead, per file or per service, for the environment where a cold start is the more expensive half.evolve-deploy rollback <config>puts the environment back on its other side without having to name a label — it works out which side is not serving, refuses when the targets disagree about that or about the version behind it, and prints what it is trading for what; both it andtraffic --tostart the revision they are handing traffic to and wait for it, since the side being rolled back to is normally stopped, and both switch off what is no longer serving once it has moved — which is also how to clean up an app carrying old revisions with no label left on them, by pointing it at the side it is already on. The choreography is one piece of code on all three and the config is the same, but who owns the traffic is not, and that changes what a rollback is. On Cloud Run the tool owns the tagged traffic block just as it owns the Container Apps one, the staged address comes back from the API instead of being assembled, and a revision without traffic scales itself to zero — sokeep_warmis refused there, because keeping one warm isscaling.min_instance_counton the template and Terraform owns that. ECS is the other family: it has a blue/green engine of its own, so the tool declares the rollout rather than driving it and answers the gate ECS offers — a PAUSE lifecycle hook at POST_TEST_TRAFFIC_SHIFT, test traffic fully on the new side and production traffic still on the old one, with no Lambda and no appspec. Because ECS owns both target groups and terminates the old tasks itself,test_urlon the target is required (a listener rule is not an address),strategy.envper side is refused (per-side environment needs sides that alternate),keep_warmis refused in favour ofstrategy.bake_time(a window rather than a standing state, and setting both is a validation error), andtraffic --todoes not apply because there is no side to name.rollbackdoes apply and takes the other shape there: for as long as the deployment has not finished the previous version is still running, so it asks ECS to put the traffic back — which covers the bake window after a switch and a release whose pipeline died at the smoke gate. Once ECS has finished the old tasks are gone and going back is a deploy, which the command says with the line to run rather than failing. Sobake_timeis also how longrollbackkeeps working. Terraform still owns the routing everywhere: on ECS the tool reads the alternate target group, both listener rules and the role off the service and refuses when any is missing. A release is one release: everything is staged first, then every smoke command runs, then all of the traffic moves — including services with nothing to change, staged at the version they already run, because the side belongs to the environment and a side missing an app is not a stack you can point a smoke test at. Nothing is staged when nothing changed, so a second apply is still a no-op. Active is the label with 100% of the traffic; a split, an environment whose apps disagree about which side is idle, and adepends_onbetween two blue-green services are all refused while planning.strategy.env.<label>gives each side the variables it needs to reach its own downstream — the router on green reading the green graph, the storefront on green calling the green router — additively, and excluded from the diff because those values differ by side by definition. Every blue-green target also getsEVOLVE_DEPLOY_SIDE.evolve-deploy trafficshows which side is serving and--to <label>puts it on a side by name, which is the way out of a split. Hooks get{{.label}}and{{.previous_label}},smokegates the release rather than a service, so it lives at file level, runs once, and names what it wants with{{url \"site\"}}because a release has no single address,--var name=valuepasses in what a deploy config cannot know, and hook lines are now rendered while planning so a mistyped variable fails the plan instead of anafterhook on a release that already succeeded.
v0.3.0
v0.3.0 - 2026-08-18
Added
- A service can name others with depends_on, so a frontend rolls out only once the backends it reads from have finished. Ordering only — a dependency that is not part of the run is already satisfied, and an unknown name or a cycle fails while reading the config.
v0.2.0
v0.2.0 - 2026-08-18
Added
- Container Apps deploys now fail as soon as Azure knows a revision is broken, instead of waiting out the ten minute readiness timeout, and the error carries the revision's provisioning error or the container's own state and restart count.
- Hook output is prefixed with the service it came from, so three package managers printing at once stay readable. Lines are also buffered until complete, so one hook can no longer cut another's line in half.
Changed
- Before hooks are now the gate for the whole release rather than for one service — every hook runs first, and if any of them fails nothing is deployed at all. After hooks are unchanged, per service and only on success.
Fixed
- A failed Container Apps deploy no longer pays the full readiness timeout twice — the rollback gets its own two minute budget — and a service reverting several targets now does them concurrently instead of one at a time.
v0.1.0
v0.1.0 - 2026-08-17
Added
- Initial release — stateless deploys with an AWS driver for ECS and Lambda, reference resolution, plan/apply separation, per-component hooks and rollback.