Running a Flutter App in GitHub Codespaces
How to set up a full Flutter development environment in the browser for workshops, codelabs, and quick experiments.
Almost every time I run a Flutter workshop, the first 20 minutes disappear into setup problems. Someone’s Flutter version is outdated. Someone else doesn’t have the Android emulator configured. Someone is on a work laptop with restricted permissions. By the time everyone is on the same page, half the energy in the room is gone. And this is actually a better scenario because sometimes I get audiences that couldn’t even be bothered with the setup. Hence, I end up with just half the team participating.
I needed a better approach for DevFest Mauritius because I was running the Intro to GenUI with Fluttter codelab which definitely requires Flutter, Dart, and Firebase. Not exactly a lightweight setup. And I did not want to leave anyone behind!
The solution I settled on was using GitHub Codespaces.
Codespaces provides a secure development environment through its built-in capabilities and native integration with GitHub. It provides a full VS Code environment in the browserwith the requirements pre-installed and anyone with a GitHub account can open it in two minutes. Local SDKs and emulators are not needed, and there are no permission issues. When you need to work on a new project, pick up a new task, or review a PR, you can simply spin up a Cloud-hosted environment, and GitHub Codespaces takes care of configuring it correctly.
So this article walks through how to set up codespaces for a basic Flutter web app and for a Firebase-connected app, like what I did with the attendees of the GenUI codelab workshop.
Why Codespaces?
As mentioned above, GitHub Codespaces spins up a containerised development environment in the cloud. So you will get a full VS Code instance in your browser, including the terminal and any other tools you configure in a .devcontainer/devcontainer.json file.
For Flutter specifically:
- Flutter Web runs in a browser tab, so no mobile emulator is needed.
- One can have Flutter, Dart, the Firebase CLI, etc. pre-installed.
- With containers, every attendee gets an identical environment. Dependencies and SDK versions will be aligned for everyone.
- It’s affordable. It’s free for 60 hours/month on a GitHub account, which a lot of developers use as their version control system.
The trade-off, however, in this case especially, is that the first load for a Flutter web app in debug mode takes a few minutes. Perhaps 1–2 minutes (if blessed with capable internet speeds). This is to allow Dart to compile to JavaScript. But once it’s loaded, hot reload and hot restart will work as normal.
Also, the codespace tends to timeout with a period of inactivity, so if you didn’t save the environment variables, you might need to redo that step every single time.
The free plan is also a bit limited, but honestly it’s not that bad. You wouldn’t be able to build big projects quickly with it, unless you pay for the Pro plan.
Basic Flutter Web App
Add a devcontainer to your repo
In your Flutter project, create .devcontainer/devcontainer.json a file which might look like this:
{
"name": "Flutter",
"image": "ghcr.io/cirruslabs/flutter:stable",
"customizations": {
"vscode": {
"extensions": [
"Dart-Code.dart-code",
"Dart-Code.flutter"
]
}
},
"forwardPorts": [8080],
"portsAttributes": {
"8080": {
"label": "Flutter Web App",
"onAutoForward": "openPreview"
}
}
}The ghcr.io/cirruslabs/flutter:stable image includes Flutter stable, Dart, and the standard toolchain.
Open in Codespaces
Push the .devcontainer folder to your repo on GitHub.
Then do either of the following:
- Go to that repo on GitHub, followed by: Code → Codespaces → Create codespace on main
- Add a one-click badge to your README for users to be redirected there when they click on it.
[](https://codespaces.new/Kymoraa/genui-codelab)Run the app
Open the terminal in Codespaces and run:
flutter run -d web-server - web-port 8080 - web-hostname 0.0.0.0 - web-renderer htmlWhat do these flags mean, you might ask. Well …
- web-hostname 0.0.0.0 binds to all interfaces, so Codespaces port forwarding can reach it. Without this, Flutter binds to `localhost` inside the container, and the forwarded URL hangs.
- web-renderer html avoids a Flutter CanvasKit/WebGL bug that causes console errors in Codespaces. The app looks and behaves identically. I run into the bug a few times before I discovered this flag. It wasn’t a breaking bug though.
Make the port public
Go to the Ports tab (next to the terminal). Then right-click on the port 8080 and change the port visibility from private to public. Then proceed to open the URL via the globe icon.
By default the port is private; setting it to public lets anyone with the URL open it directly — useful for sharing during a live workshop.
N.B. The first load will show a blank page for 1–2 minutes while Flutter compiles (as we noted above). This is quite normal. Wait, then refresh. Once it loads, hot reload (r) and hot restart (R) work from the terminal as usual.
Get Jackie Moraa’s stories in your inbox
Join Medium for free to get updates from this writer.
Adding Firebase
If your app uses Firebase, like in the case of the GenUI codelab, which uses Firebase AI Logic, then there are a couple of Codespaces-specific steps to know about.
Firebase CLI
The cirruslabs/flutter image doesn’t include Node.js, so npm install -g firebase-tools won’t work. Use the standalone installer instead:
curl -sL https://firebase.tools | bashAdd this as a postCreateCommand in your devcontainer.json file so it’s pre-installed for every user:
postCreateCommand": "curl -sL https://firebase.tools | bash && flutter upgradeYour file will look like this.
Firebase authentication
Codespaces is a headless environment; therefore, the standard firebase login flow tries to open a localhost callback URL, which doesn’t exist inside the container. You’ll see the error on the terminal. Instead, use the CI token flow:
firebase login:ci - no-localhostThis prints a URL on the terminal. Open it in your local browser, sign in with Google, copy the authorisation code, and paste it back into the Codespaces terminal. Firebase will then print a long-lived token.
Export it immediately and save it to ~/.bashrc so that it survives session restarts:
export FIREBASE_TOKEN=your-token-here
echo 'export FIREBASE_TOKEN=your-token-here' >> ~/.bashrcNow, all firebase commands in the session will have it and use it automatically.
FlutterFire CLI
If you need flutterfire configure, activate it with:
dart pub global activate flutterfire_cliThen add the pub cache to your PATH (this isn’t in the default PATH in the container):
export PATH="$PATH":"$HOME/.pub-cache/bin"
echo 'export PATH="$PATH":"$HOME/.pub-cache/bin"' >> ~/.bashrcApp Check
The next issue I bumped into while setting this up is with regards to Firebase App Check. Firebase AI Logic (and other Firebase services) can have App Check enforced by default on new projects.
And with this, every AI request was returning a 401 Error in the console. The app appeared to work but the AI was never responding. The fix for this was to switch Firebase AI Logic from Enforced to Monitor mode.
Firebase Console → your project → Build → App Check → APIs tab →
Firebase AI Logic → Show details → Set up → switch to Monitor.This lets requests through while still logging metrics.
Rate limits
Still on the list of issues I faced, which shouldn’t be that unfamiliar to you if you work with these AI models a lot, are rate limits. The Gemini free tier allows 20 requests/minute. So exhausting it while testing the app and setup was shown by the 429 Error on the console. If this happens to you, wait a couple of minutes and try again or switch the Gemini model, gemini-2.5-flash, gemini-3.5-flash, gemini-3.5-flash-lite etc.
Resuming after idle
Codespaces suspends after 30 minutes of inactivity. Files and installed tools are preserved, but:
- Environment variables (
FIREBASE_TOKEN,PATH) are lost - Running processes (
flutter run) are stopped - Port visibility resets to
private
So, when you reconnect, you’ll need to run: source ~/.bashrcThen set port 8080 back to public in the ports tab, and restart the app: flutter run -d web-server — web-port 8080 — web-hostname 0.0.0.0 — web-renderer html
If you need to regenerate a Firebase token (e.g., in an instance where you didn’t save it to ~/.bashrc), just run firebase login:ci — no-localhost again and it will generate a new token without invalidating old ones.
I’ll admit. This setup is nowhere near perfect, but for a workshop where you can’t control attendees’ machines, this one-click environment is worth the trade-offs. We didn’t get to finalise all the steps in the codelab because just like all other workshops, with multiple users who move at different speeds, time was not our greatest ally.
However, every attendee was at a comfortable spot to finish the assignment during their free time. And unlike other scenarios where I only end up with a few people participating, this workshop had everyone engaged with apps running, whether they had worked with Flutter before or not, without them spending half the time installing SDKs. That was the goal!
If you are interested in trying out the codelab using codespaces, you can find the project here on GitHub. Follow the steps on the README (or this article), and you should be good to go!
People kept saying ‘it works on my machine,’ so we just decided to ship their machine.
— Dev Community
Thank you so much for reading! ❤
References
1. https://github.com/Kymoraa/genui-codelab
2. https://github.com/features/codespaces
3. https://docs.github.com/en/codespaces/about-codespaces/what-are-codespaces
4. https://code.visualstudio.com/docs/remote/codespaces
5. https://webcodespace.com/using-github-codespaces-for-flutter-development/










