Sign inSign up

alihaidar199527/flutter-devcontainer

By alihaidar199527

•Updated 23 minutes ago

Base Flutter dev container image with Android SDK, Web, Java, Node and Firebase. amd64 + arm64.

Image
Languages & frameworks
Developer tools
0

1.3K

alihaidar199527/flutter-devcontainer repository overview

⁠flutter-devcontainer

Blank-canvas base Docker image for VS Code Dev Containers. One image, shared across all your Flutter projects.

Flutter (stable) · Dart · Android SDK 36 · Java 21 (Temurin) · Node.js 24 LTS · pnpm (Corepack) · Firebase CLI · FlutterFire CLI · GitHub CLI · Starship

Docker Build Docker Pulls Image Size License: MIT

docker pull alihaidar199527/flutter-devcontainer:latest

⁠Overview

Every Flutter project needs the same developer tooling: Flutter SDK, Android SDK, Dart, Java, Gradle, Firebase, and a productive terminal. Configuring all of this from scratch on every machine — or worse, on every project — wastes time and produces inconsistent environments.

This repository solves that problem with a single shared base image. Merge an image change to main and every project that uses latest gets the upgrade on its next docker pull — without touching any project code. Projects pinned to a permanent tag⁠ keep their image until you bump the tag.

This repo has one job: build and publish the base development Docker image to Docker Hub.

It contains no Flutter project files, no pubspec.yaml, no application code, and no app-level CI. Flutter packages, app configuration, and deployment workflows all belong in the project repositories that consume this image.


⁠Quick start

The fastest route is the flutter-template⁠ repository, which already wires this image into a Dev Container. To use the image in your own project, point a Dev Container at it:

// .devcontainer/devcontainer.json
{
  "name": "Flutter",
  "image": "alihaidar199527/flutter-devcontainer:latest",
  "forwardPorts": [8080]
}

The image's metadata supplies the developer user, the Dart and Flutter extensions and the Flutter SDK path, so nothing else is required. Port 8080 is where frunw serves the web target. latest always moves to the newest build; a project that must keep building on the same image pins a permanent tag⁠ instead (flutter-X.Y.Z.R or a digest), for example "image": "alihaidar199527/flutter-devcontainer:flutter-3.47.6.1".

Without Dev Containers, run any tool straight from the image:

docker run --rm -it -v "$PWD:/workspace" alihaidar199527/flutter-devcontainer:latest bash

⁠Architecture

This image is one half of a two-repo system.

RepoResponsibility
flutter-devcontainer ← you are hereBuild and publish the base dev image
flutter-templateGitHub Template — the starting point for every new Flutter project

When you open a Flutter project that uses this image, the container starts via Docker Compose with your project folder mounted at /workspace and your Git identity and SSH keys available for pushing to GitHub.

The container user is developer with UID/GID 1000 (the base image's node account, renamed), which matches the first user on most Linux hosts, so files created in /workspace belong to you on the host as well. On Docker Desktop (Windows/macOS) bind-mount ownership is translated by Docker and the UID does not matter. If your Linux user has a different UID, Dev Containers remaps developer to it automatically.

The image carries Dev Container metadata⁠ (devcontainer.metadata label), so a devcontainer.json or Compose service that uses it inherits remoteUser: developer, the Dart and Flutter VS Code extensions and dart.flutterSdkPath without repeating them.

Runtime defaults baked into the image, so consuming projects need no startup workarounds for them:

DefaultEffect
git config --system safe.directory /workspaceGit works in the bind-mounted workspace (no "dubious ownership" error), even when ~/.gitconfig is mounted read-only
~/.gradle, ~/.pub-cache, ~/Android, ~/.shell_history exist and belong to developerA named volume mounted over them starts writable instead of root-owned
CHROME_EXECUTABLE=/usr/local/bin/chrome-browserChromium is found by every process, including IDE-launched ones
/etc/profile.d/10-dev-toolchain.shFlutter, Java, Node and the Android SDK tools stay on PATH in login shells too (docker exec -l, su -l, SSH), where Debian's /etc/profile would otherwise reset it
ANDROID_HOME (and the deprecated ANDROID_SDK_ROOT, same value)Android tooling finds the SDK at /home/developer/Android/sdk
COREPACK_ENABLE_DOWNLOAD_PROMPT=0Corepack never waits for an interactive confirmation
pnpm prepared for developer (Corepack cache)pnpm install works offline when the project's packageManager matches the image's pnpm version

Named volumes are initialised from the image only when they are first created. After pulling a new image, recreate volumes that cache image content (for example the Android SDK or pub cache) with docker compose down -v to pick the new content up.


⁠What's Inside

⁠Runtime & Language
ToolVersionPurpose
Flutter SDKstable channelFlutter framework + Dart SDK
Dart SDKbundled with FlutterLanguage runtime (included in Flutter)
Java (Eclipse Temurin)21Required by Android build toolchain and Gradle
Node.js24 LTS (Debian 13 trixie-slim)Required by Firebase CLI and FlutterFire CLI
pnpm12.18.0 (via Corepack)Package manager for repo-level tooling (Husky, commitlint) in consuming projects
⁠Android
ToolVersionPurpose
Android SDKAPI 36Latest Android platform
Android Build Tools36.0.0APK/AAB compilation
Android Platform Toolslatestadb, fastboot
Android Cmdline Toolslatest (bootstrapped from build 15859902)sdkmanager, avdmanager
Gradleper projectNo standalone Gradle is installed: each Flutter project's wrapper (android/gradlew) downloads the version Flutter chose into the persisted ~/.gradle cache on the first build
⁠Web & Testing
ToolVersionPurpose
ChromiumDebian 13 build (amd64 and arm64)flutter test --platform chrome, flutter build web checks, headless browser for web integration tests
chromedrivermatches Chromiumflutter drive -d web-server (web integration tests)
lcovDebian 13 buildgenhtml turns flutter test --coverage into an HTML report
ftestc && genhtml coverage/lcov.info -o coverage/html     # HTML coverage report

chromedriver --port=4444 &                                # web integration tests
flutter drive --driver=test_driver/integration_test.dart \
  --target=integration_test/app_test.dart -d web-server --browser-name=chrome --headless
⁠Developer Tools
ToolVersionPurpose
Firebase CLI15.35.0Firebase project management and deployment
FlutterFire CLIlatestConfigure Firebase in Flutter projects
pnpm12.18.0Fast, disk-efficient package manager, activated via Corepack at build time (cached for the developer user — no download on first use)
GitHub CLIlatestgh pr create, gh run watch, gh auth login
openssh-client—git push via SSH from inside the container
Starship1.27.0Terminal prompt — git branch and status, Dart and Node versions
Utilities—curl, git, jq, less, nano, htop, tree, procps, bash-completion (git and gh tab completion)
⁠What is NOT inside

These are intentionally absent. Add them to your pubspec.yaml per project:

provider / riverpod / bloc    →  flutter pub add provider
go_router                     →  flutter pub add go_router
dio / http                    →  flutter pub add dio
hive / isar / drift           →  flutter pub add hive
firebase_core                 →  flutter pub add firebase_core
any_other_package             →  flutter pub add <package>

⁠Platform Support

PlatformBuild targetStatus
Android APK / AABflutter build apk, flutter build appbundle✅ Full support
Webflutter build web, flutter run -d web✅ Full support
iOSflutter build ipa❌ Requires macOS + Xcode — impossible in Linux containers
Linux Desktopflutter build linux❌ Not included — a container cannot show a desktop window, so the toolchain is left out
macOS Desktopflutter build macos❌ Requires macOS
Windows Desktopflutter build windows❌ Requires Windows host

The three desktop platforms are switched off in the image's Flutter configuration, so flutter create generates only the android, ios and web folders and flutter doctor reports no desktop toolchains as missing.

⁠iOS Note

Apple's build toolchain (Xcode, codesign, xcodebuild) only runs on macOS by Apple's own enforcement — this is not a tooling gap. iOS is handled at the CI layer:

  • CI: Use a macos-latest GitHub Actions runner in your flutter-template CI workflow to build, sign, and upload to TestFlight automatically on every push to main.
  • When you get a Mac: The same devcontainer works identically. For iOS, also install Xcode on the Mac host — flutter doctor detects it automatically. No image changes needed.
  • No Mac at all: Rent one for $1–5/hour on Codemagic⁠ or MacInCloud⁠ for the one-time certificate and provisioning profile setup.

⁠Platforms

PlatformArchitecture
Windows · Linux · GCPlinux/amd64
Apple Silicon Maclinux/arm64

Docker pulls the correct platform automatically.


⁠Tags

TagPublished when
latestEvery publish — always the newest build
flutter-X.Y.Z.REvery publish — immutable: Flutter release X.Y.Z and image revision R (for example flutter-3.47.6.1). R starts at 1 for each Flutter release and every new build of that release takes the next free number (.2, .3, …). The tag to pin an app to; the dotted numeric form lets Dependabot in a consuming repository order and bump it
buildcache-amd64, buildcache-arm64Internal layer cache for the publish workflow — not meant to be pulled

Every publish from main for a push, or for the first image of a new Flutter release, also gets a GitHub Release⁠ with the same name as the tag.

Only latest moves. flutter-X.Y.Z.R and the image digest never change, and no published tag is ever deleted or overwritten, so an app pinned to one of them pulls the same image today and a year from now. A revision number is never reused: the publish workflow takes the next revision that is free on Docker Hub and can be created as a git tag, so revisions always increase but can skip numbers (for example after wiping Docker Hub, because GitHub keeps a tag name used by an immutable release reserved for good). Every image also has a git tag of the same name. The digest (alihaidar199527/flutter-devcontainer@sha256:…) is the strictest pin and appears in every run summary.

Which commit built an image? Every image carries it in the org.opencontainers.image.revision label, and the publish run's summary shows it next to the digest:

docker inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}' alihaidar199527/flutter-devcontainer:flutter-X.Y.Z.R

Choosing a tag: use latest to always get the newest Flutter and tools; use flutter-X.Y.Z.R in projects that must keep building on a known image, and bump it deliberately (the revision increases when the same Flutter release is rebuilt with operating-system or tool updates). The build date is in the image's org.opencontainers.image.created label and on Docker Hub. Keep Docker Hub tag-retention or inactive-image clean-up disabled for this repository, otherwise old tags can expire outside this pipeline.

⁠Rolling back latest

If a build reaches latest and turns out to be bad, repoint latest to the last good permanent tag. No rebuild is needed: the tag is moved on the registry, the digest (and with it the signature and attestations) is unchanged, and both architectures are kept.

# 1. find the last good build and confirm what it points to
docker buildx imagetools inspect alihaidar199527/flutter-devcontainer:flutter-X.Y.Z.R

# 2. repoint latest to it (needs push access to the Docker Hub repository)
docker buildx imagetools create \
  -t alihaidar199527/flutter-devcontainer:latest \
  alihaidar199527/flutter-devcontainer:flutter-X.Y.Z.R

The next publish from main moves latest forward again, so fix the cause on develop and promote it, or disable the Docker workflow until then. Projects pinned to a permanent tag or digest are not affected by any of this.


⁠Verifying the image

Published images are signed with Cosign⁠ using GitHub's OIDC identity (keyless), and carry SBOM and provenance attestations. To check that an image was built by this repository's publish workflow on main:

cosign verify alihaidar199527/flutter-devcontainer:latest \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp '^https://github.com/alihaidar0/flutter-devcontainer/\.github/workflows/docker\.yml@refs/heads/main$'

Inspect the attestations with docker buildx imagetools inspect alihaidar199527/flutter-devcontainer:latest --format '{{ json .Provenance }}' (or .SBOM).


⁠Shell Aliases

All aliases are defined in scripts/shell_setup.sh and baked into the image.

⁠Flutter
AliasExpands toNotes
flflutterShort flutter prefix
fgetflutter pub getInstall all dependencies
faddflutter pub addAdd a package
frmflutter pub removeRemove a package
fupgradeflutter pub upgradeUpgrade packages
foutdatedflutter pub outdatedCheck for outdated packages
frunflutter runRun on connected device
frunwflutter run -d web-server ...Run web server (Docker-friendly, port 8080)
fruncflutter run -d chromeRun in Chrome
fbuildflutter buildBuild prefix
fbuildapkflutter build apk --releaseRelease APK
fbuildaabflutter build appbundle --releaseRelease App Bundle
fbuildwebflutter build web --releaseRelease web build
ftestflutter testRun tests
ftestcflutter test --coverageRun tests with coverage
fanalyzeflutter analyze --fatal-infosStatic analysis (info-level lints fail, as in CI)
fformatdart format .Format all Dart files
fformatcheckdart format --set-exit-if-changed .Format check (CI mode)
fdoctorflutter doctor -vVerbose environment check
fcleanflutter cleanClean build cache
fcreateflutter createCreate new project
fdevicesflutter devicesList connected devices
fupgrade_sdkflutter upgradeUpgrade Flutter SDK
⁠Dart
AliasExpands to
dpubdart pub
dgetdart pub get
dformatdart format .
danalyzedart analyze
dtestdart test
drundart run
dcompiledart compile
dglobaldart pub global
⁠Firebase
AliasExpands to
fbloginfirebase login
fbdeployfirebase deploy
fbservefirebase serve
fbusefirebase use
fblistfirebase projects:list
ffinitflutterfire configure
⁠Android / ADB
AliasExpands to
adbdevicesadb devices
adblogadb logcat
adbinstalladb install
adbrestartadb kill-server && adb start-server
⁠Git
AliasExpands to
gsgit status
gagit add
gcgit commit -m
gpgit push
gplgit pull
glgit log --oneline --graph --decorate
gcogit checkout
gbgit branch
gdgit diff
⁠General
AliasExpands to
llls -alFh --color=auto
lals -A --color=auto
clsclear

⁠Troubleshooting

⁠Build fails — authentication error

Symptom: denied: requested access to the resource is denied

  1. Go to Settings → Environments → docker-hub
  2. Confirm both DOCKERHUB_USERNAME and DOCKERHUB_TOKEN are present as environment secrets
  3. Confirm the token has Read, Write & Delete scope — Read-only tokens cannot push
  4. If expired: Docker Hub → Account Settings → Personal access tokens → delete → create new → update the secret
  5. Re-run from the Actions tab
⁠Build fails — Docker Hub repository missing

Symptom: the publish job fails at the push step with denied or repository does not exist, for example after the Docker Hub repository was deleted.

Create the repository alihaidar199527/flutter-devcontainer as Public (see docs/github-setup.md⁠), then re-run the failed jobs. After the first successful publish, run Docker Hub Description once so the overview shows the README.

⁠Manual run refused — branch not allowed to deploy to docker-hub

Symptom: a manual Docker or Docker Hub Description run stops with "Branch develop is not allowed to deploy to docker-hub".

The Branch dropdown of Run workflow defaults to the default branch, develop, and the environment accepts main only. Choose main and run again.

⁠The release job says the release already exists

The release for a tag is created once and never replaced, so a re-run of the release job for a tag that already has a release shows this notice and does nothing. It is harmless.

⁠The publish stops in the "Reserve the image tag" job

The job decides the image name before anything is pushed:

  • Unexpected answer from Docker Hub (HTTP …): Docker Hub was unreachable or rate-limited. Re-run the failed jobs.
  • Could not reserve flutter-… with Resource not accessible by integration: the job lacks permission to create the git tag. It requests contents: write itself; check Settings → Actions → General and any rulesets that restrict creating flutter-* tags (tags-protect must not restrict creations).
  • No free revision: 99 revisions of one Flutter release are taken. Wait for a newer Flutter or raise MAX_REVISION in .github/scripts/reserve-tag.sh.

A name that GitHub refuses because it belongs to a deleted immutable release is skipped automatically (a notice says so); it is not an error.

⁠Permission denied on /workspace or in a named volume

Images published before the user-ID change ran developer as UID 1001. A named volume created with such an image stays owned by 1001, and a Linux host user with UID 1000 could not write the project files. Current images use UID/GID 1000.

  1. Check the user inside the container: id must show uid=1000(developer) gid=1000(developer). If it does not, pull the current image and rebuild the dev container.
  2. Recreate volumes made by an older image: docker compose down -v, then start the container again. Only cached data is lost (pub packages, Gradle artifacts, shell history).
  3. If your host user is not UID 1000, make sure updateRemoteUserUID is not set to false in devcontainer.json; the default remaps the container user to yours.
⁠Build fails — checksum mismatch

Symptom: sha256sum: WARNING: 1 computed checksum did NOT match while building the Android or Starship layer.

A pinned download no longer matches the checksum recorded in docker/Dockerfile.dev. This is the check doing its job: either the version and checksum were bumped separately (copy both from the official source — see Updating the Image⁠), or the upstream file changed unexpectedly, in which case do not bump the checksum until you know why.

⁠flutter doctor shows Android licenses not accepted

Inside the container, run:

yes | flutter doctor --android-licenses

This is already handled at image build time but may be needed after an sdkmanager update.

⁠pnpm: command not found in a project's postCreateCommand

This means the image was built before Corepack/pnpm activation was added, or the container is running against a stale cached image. Pull the latest image (docker pull alihaidar199527/flutter-devcontainer:latest) and rebuild the dev container. If it's still missing, run corepack --version inside the container to confirm Corepack itself is present before filing an issue.

If pnpm starts downloading itself on first use, the image predates the build-time cache fix (pnpm is now prepared as the developer user). Pull a current image, or check with COREPACK_ENABLE_NETWORK=0 pnpm --version, which must print the pinned version.

⁠ADB cannot find device (connecting to host emulator)

Run the Android emulator on your host machine, then inside the container:

adb connect host.docker.internal:5555
adbdevices

If shown as unauthorized, accept the prompt on the emulator screen. If shown as offline:

adbrestart
adb connect host.docker.internal:5555

On Windows, ensure your firewall allows inbound TCP on ports 5037 and 5555 from the Docker network.

⁠flutter run -d chrome shows no window

The container has no display, so a browser window cannot open inside it. Use the web server target and open the page in your host browser:

frunw   # flutter run -d web-server --web-port 8080 --web-hostname 0.0.0.0

Then open http://localhost:8080. Chromium is still used headlessly inside the container for flutter test --platform chrome and web integration tests.

⁠git push fails — permission denied (publickey)
ssh-add -l          # check loaded keys
ssh -T [email protected]   # verify auth

On Windows, ensure the SSH agent is running and your key is loaded before opening VS Code:

sc config ssh-agent start= auto
net start ssh-agent
ssh-add "$env:USERPROFILE\.ssh\id_ed25519"
⁠Out of disk space during build

Flutter SDK + Android SDK together are ~4–5 GB. Ensure Docker Desktop has at least 20 GB of disk image space allocated: Docker Desktop → Settings → Resources → Disk image size


RepoPurpose
flutter-devcontainer⁠← You are here — builds the Docker image
flutter-template⁠Flutter project template — pulls this image for development

Flutter stable · Dart · Android API 36 · Java 21 Temurin · Node.js 24 LTS · pnpm 12.18.0 · Debian 13 Trixie · 2026


The full documentation (the build pipeline, updating the image, the repository setup) is in the GitHub README⁠.

Tag summary

Content type

Image

Digest

sha256:afc8b0d28…

Size

2.2 GB

Last updated

23 minutes ago

docker pull alihaidar199527/flutter-devcontainer