You've built a beautiful app in Flutter. Now comes the part nobody talks about: the release. Signing, stores, version numbers, secrets... and that one person who “knows how to do the build”. Let's see how a CI/CD pipeline — with Fastlane on mobile — turns shipping Flutter apps (and software in general) into a reliable, fast system that doesn't depend on whoever runs it.
From the classic “It works on my machine” meme to the reproducible build
There's a moment, in almost every team that builds software, when the release turns into a shamanic ritual. There's one person who “knows how to do the build”, who maybe is also the only one with the app signing keys on their laptop, who knows their passwords by heart, who remembers which environment variables need to be set before kicking off the build, and so on. As long as that person is in the office, everything runs smoothly. The day they're on vacation... disaster! The release grinds to a halt, and the product goes on vacation with them.
This isn't an abstract hypothesis: unfortunately it's the snapshot of a great many organizations that might even write excellent code. The problem, in fact, isn't technical, it's structural. Often, even at the academic level, how to manage software — and above all how to take something from a development environment to a production system — gets glossed over. The knowledge of how to deploy software often lives in the head of a single person (a senior developer or a sysadmin) instead of being baked into the system.
A CI/CD pipeline exists precisely to move that knowledge out of the head of the individual who has already banged their head against it a thousand-plus times, and to funnel it all into a repeatable, verifiable and accessible tool. In this article I'll try to explain why it's worth adopting a CI/CD pipeline, starting from real problems that we at Codebaker have run into in the past and from how we solved them.
What we really mean by CI/CD
The acronym is overused, so it's worth pinning down the terms. Continuous Integration means that every change to the code is integrated frequently into the main branch (and, of course, validated). This includes code and commit hygiene, compilation, testing, and static and dynamic analysis of the software. Continuous Deployment means that from that same change you arrive, in an automated way, at an artifact ready to be released — and in some cases all the way to the actual publication on the stores or on the server.
The heart of it all is a service that, in response to an event (a commit, a tag, a merge to a staging branch, and so on) runs an “always identical” sequence of steps, described in a file that is versioned alongside the code. We're not talking about a wiki with the steps to follow or some arcane documentation: we're talking about code. If we can boil it all down to a block of code, then it will be readable, modifiable and, above all, reproducible by anyone and from any machine suited to that kind of task. (Ahem... Apple and iOS builds...)
The value of automating builds
Imagine a pipeline that builds a Flutter app and uploads it to the stores. Done by hand, the process is a long, fragile chain: you have to generate the artifacts, and right away the first choices open up — sub-packages for staging, keys for Firebase, localization, internal libraries vs pub packages, and so on and so forth. On top of that, even before compilation there are operations like aligning the version number that demand attention. Then these packages have to be signed with the right keys for Android and iOS, uploaded to the respective stores with the correct metadata (screenshots, changelogs, policy updates). Each step can break in a different way on different machines and with different humans.
For example, if you've ever tried to build an app on an ARM Mac you already know the repertoire: native dependencies that behave differently than on x86, Ruby versions installed under emulation instead of natively, toolchains with different versions across the various machines in the office. The result is the classic “it all works fine on my machine, why not on yours?”. It's no one's incompetence: it's simply the effect of non-reproducible environments.
A pipeline breaks this vicious circle precisely because it defines a single canonical build environment, even if the machine that runs the release isn't always the same: for the first time there will be a system with the same SDK versions, the same environment variables, and so on — and often none of that is guaranteed even by a pub.lock, for instance.
The question “who does the build?” stops mattering, because the answer will always be: the pipeline. The developer no longer signs anything from their own laptop; they push the code and the system does the rest, in the same way, for everyone.
This has a precious side effect: configuration problems get discovered once and solved forever. When a release build fails because the signing keystore isn't found in the right path, or because a staging flavor was misconfigured, that fix ends up in the pipeline file. From that moment on, the problem will never resurface, on any machine. The debugging isn't wasted: it becomes part of the system's heritage.
Fastlane: the Flutter for Flutter (and beyond)
In the mobile world, the tool that has set a de facto standard for this kind of automation is Fastlane. It was born to solve exactly the problems described above: signing, building, versioning and publishing iOS and Android apps without having to remember dozens of different commands. There are already operations like, for example, fastlane match that abstract away operations which would otherwise be downright arcane.
Fastlane's strength lies in the concept of the lane: a “lane” is a sequence of steps with a name. You write lane :deploy_staging once and from that moment anyone, or any CI server, can run it with a single command. Inside the lane you run ready-made actions: build the release app bundle, bump the version following the Git tag, publish the app to Google Play or App Store Connect with the correct changelogs, and much more.
What used to be a ten-step manual document — “generate the AAB, check it's signed in release and not in debug, verify the package name, upload to the internal track, update the release notes” — becomes a handful of versioned lines of code. And like any code, they can be reviewed, tested and improved incrementally.
Secrets management: the point where everything can go wrong
Here we get to the most delicate topic. A release pipeline needs secrets: the keystore password, the key alias, the store credentials file, any access tokens for external services and other critical values to include in the build. They're the keys to the kingdom. If they end up in the wrong place, the damage is permanent (or at least until you notice and rotate every credential).
The correct approach cleanly separates two worlds:
- The code, which describes how a secret is used. For example, a signing configuration that reads the password from a variable, or a path that points to a credentials file. This lives in the repository, because it reveals nothing.
- The secret values, which live only in the CI's secure environment. CI/CD systems offer protected variables or dedicated vaults: values encrypted at rest, masked in logs, injected into the build environment only at execution time and only for the jobs entitled to them.
The file with the secrets, in this model, is generated by the pipeline on every run from the protected variables, and doesn't exist anywhere else. The real keystore lives in a secure location on the build agent, outside the code checkout. The developer never sees the values in cleartext, and that's exactly the goal: minimize the number of people who have access to the critical material, without thereby preventing anyone from kicking off a build.
The guiding principle is least privilege: every person and every job should be able to do exactly what's needed, nothing more. The pipeline becomes the only point at which secrets are used, and therefore also the only one at which they need to be exposed.
Protecting production: not all branches are equal
There's a second layer of security, complementary to secrets management, that to a superficial eye might seem off-topic, and it concerns who can trigger a production build. Even with secrets well guarded, if anyone can push code to the branch that triggers the production deploy, the risk stays enormous — both as a matter of security and of plain human error.
The answer is branch protection. You configure the version control system so that the main branch doesn't accept direct pushes from developers. Every change has to go through a pull request, pass the checks and receive the approval of at least one other person. This way, the code that reaches production has always been seen by more than a single pair of eyes and has always definitely passed the tests.
You then tie the production deploy to controlled events: for example only to a merge on the protected branch, or only to the creation of a version tag, perhaps with an explicit manual approval as a final check. Staging or development environments, on the other hand, can be more permissive: in fact it's advisable to let developers build continuously, even on every commit, because an error there has no consequences for real users and is part of the normal software development cycle.
The result is a clean separation between being able to contribute and being able to release to production. The whole team obviously has to be able to work, propose changes, kick off staging builds. But the final gate toward users is governed by explicit rules, written and enforced automatically, not by anyone's trust or memory. Here too the point isn't to distrust your colleagues, but not to make security depend on individual discipline.
Eliminating pointless manual work
Pulling the threads of this article together, much of the value of a CI/CD pipeline boils down to one sound principle: what a machine can do reliably shouldn't be done by a person. Not because people aren't capable, but because repetitive manual work is fertile ground for errors, and it's a hidden cost that grows with every release, with every change, and in some cases piles up even more technical debt than the code itself.
You forget to bump the version number by hand. You make a mistake copying the keystore into the right folder by hand (assuming you don't keep everything on your Desktop). You waste time (honestly, time spent badly) aligning the release notes across environments. Each of these micro-tasks, taken on its own, seems trivial: put together, and multiplied by the frequency of releases, they become hours stolen from real development and a constant source of avoidable incidents.
By automating them, three things happen. The release becomes fast: what used to take half a day of attention is resolved in a few unattended minutes (after, of course, the necessary quality tests). It becomes reliable: the machine doesn't get distracted and doesn't skip steps. And it becomes frequent: when releasing costs little and is no longer scary, you release often, in small increments, which are easier to verify and less risky than the big releases accumulated over time.
Conclusion
A CI/CD pipeline, with tools like Fastlane acting as the engine on the mobile side, isn't a big-company job or a whim for perfectionist engineers. It's how you turn software releasing into a system: reproducible, secure, independent of whoever runs it and of the machine it runs on.
The three pillars hold each other up. A canonical build environment makes builds agnostic to whoever runs them. Disciplined secrets management keeps the keys out of the wrong places and off individual developers' laptops. Branch protection separates those who contribute from those who release to production. All of this together eliminates pointless manual work and the dependence on that one person who “knows how it's done”.
The right moment to build this infrastructure isn't when the release breaks for the first time. It's earlier: when the product is still small (or maybe doesn't even exist yet) and the pipeline can be written in an afternoon, fixing the errors right away. But even if you're already in the shamanic-ritual phase, it's still worth it: every problem you solve once in the pipeline is a problem that will never torment you again.
Davide Antonio Amodio
Want to automate your releases?
At Codebaker we design CI/CD pipelines and infrastructure that make releases fast, secure and independent of whoever is on duty. If you want to get out of the deploy “shamanic ritual”, let's talk.
Contact us