Sign inSign up

alihaidar199527/flutter-devcontainer

By alihaidar199527

•Updated 14 days ago

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

Buildkit cache
Image
Languages & frameworks
Developer tools
0

783

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

⁠Contents


⁠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.9.1 (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.32.1Firebase project management and deployment
FlutterFire CLIlatestConfigure Firebase in Flutter projects
pnpm12.9.1Fast, 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.26.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.

⁠Repository Structure

flutter-devcontainer/
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml                ← Structured bug report form
│   │   ├── feature_request.yml           ← Structured feature request form
│   │   └── config.yml                    ← Disables blank issues, links to flutter-template
│   ├── rulesets/
│   │   ├── main-protect.json             ← Importable ruleset: protects main
│   │   ├── develop-protect.json          ← Importable ruleset: protects develop
│   │   └── tags-protect.json             ← Importable ruleset: protects release (`flutter-*`) tags
│   ├── scripts/
│   │   ├── audit_gate.py                 ← CI: fails on high/critical npm advisories that are not allowlisted
│   │   ├── check-sync.sh                 ← CI: README versions/aliases must match the implementation
│   │   ├── check_drift.py                ← Compares pinned versions with upstream (used by dependency-drift.yml)
│   │   ├── reserve-tag.sh                ← Picks the next free image revision and reserves its git tag (used by docker.yml)
│   │   ├── smoke-test.sh                 ← Toolchain smoke test shared by ci.yml and docker.yml
│   │   └── test-reserve-tag.sh           ← CI: tests reserve-tag.sh with stubbed curl and gh
│   ├── workflows/
│   │   ├── ci.yml                        ← PR validation: lint, format, docs sync, build + smoke test + scan, npm audit → "CI passed"
│   │   ├── dependency-drift.yml          ← Weekly: reports pinned tool versions that are behind upstream
│   │   ├── docker.yml                    ← Builds natively per arch, tests, pushes, signs (push to main when the image changes, or manual)
│   │   ├── dockerhub-description.yml     ← Syncs README.md to Docker Hub on push to main
│   │   ├── labels.yml                    ← Syncs labels.yml to GitHub labels
│   │   ├── pr-labels.yml                 ← Adds labels to a pull request from its Conventional Commit title
│   │   └── release.yml                   ← Reusable: publishes the GitHub Release named after the image (called by docker.yml)
│   ├── CODE_OF_CONDUCT.md                ← Contributor Covenant 2.1
│   ├── CODEOWNERS                        ← Auto-requests reviewer on every PR
│   ├── CONTRIBUTING.md                   ← Branching, commit and change guidelines
│   ├── grype.yaml                        ← Image-scan exceptions for components that cannot be fixed here, each with its reason
│   ├── npm-audit-allowlist.txt           ← Accepted high/critical advisories, each with its reason
│   ├── PULL_REQUEST_TEMPLATE.md          ← PR checklist (versions, platforms, scope)
│   ├── dependabot.yml                    ← Weekly grouped updates for GitHub Actions + Docker base image → develop
│   ├── renovate.json                     ← Weekly PRs for the pins Dependabot cannot see (firebase-tools, pnpm, node digest) → develop
│   ├── labels.yml                        ← Label definitions — name, color, description
│   └── release.yml                       ← Release-notes categories (by PR label)
├── docker/
│   ├── Dockerfile.dev                    ← The image recipe ← MAIN FILE
│   └── firebase-tools-overrides.json     ← npm overrides that patch firebase-tools' transitive dependencies
├── docs/
│   └── github-setup.md                   ← Setup and recovery guide: GitHub, Docker Hub and Renovate settings, rulesets, bootstrap order
├── scripts/
│   └── shell_setup.sh                    ← Installs Starship (pinned + verified) and bakes aliases into the image
├── .dockerignore                         ← Allowlist: only what the Dockerfile COPYs enters the build context
├── .editorconfig                         ← Consistent indentation/line endings across editors
├── .gitattributes                        ← Forces LF line endings in every checkout
├── .gitignore                            ← Ensures secrets are never committed
├── .hadolint.yaml                        ← Dockerfile lint rules used by CI
├── LICENSE                               ← MIT — free to use, your name stays on it
├── README.md                             ← This file — also synced to Docker Hub
├── SECURITY.md                           ← Vulnerability reporting policy
└── repomix.config.json                   ← Repomix config for generating a single-file repository snapshot

⁠GitHub Automation

⁠Workflow trigger summary
FileTriggerWhat happens
workflows/ci.ymlPR targeting develop or mainLint, format, docs sync, build + smoke test + vulnerability scan (amd64 + arm64), npm audit, then the aggregate CI passed check
workflows/ci.ymlManual dispatchSame checks on the selected branch
workflows/docker.ymlPush to main (docker/, scripts/, .dockerignore or docker.yml changed)Builds each architecture natively, smoke-tests, pushes :latest and a permanent :flutter-X.Y.Z.R tag, and signs the image
workflows/docker.ymlManual dispatch (from main)Same pipeline, with force-rebuild and push toggle
workflows/dependency-drift.ymlEvery Monday 06:00 UTC / manualOpens, updates or closes one issue listing pinned tool versions that are behind upstream
workflows/dockerhub-description.ymlPush to main (README.md changed)Updates Docker Hub description
workflows/dockerhub-description.ymlManual dispatch (from main)Forces immediate Docker Hub sync
workflows/release.ymlCalled by docker.yml after a publish from mainPublishes a GitHub Release named after the image (flutter-X.Y.Z.R) with generated notes
workflows/labels.ymlPush to main (.github/labels.yml changed)Syncs all labels to GitHub
workflows/labels.ymlManual dispatchBootstrap all labels in one go
workflows/pr-labels.ymlPull request opened, edited or reopened (into develop or main)Adds labels from the Conventional Commit title so the release notes are grouped without manual work
dependabot.ymlEvery Monday 09:00 UTCScans GitHub Actions and the Docker base image, opens grouped PRs against develop
renovate.jsonBefore 09:00 UTC on MondaysRenovate opens one PR per bump of firebase-tools, pnpm or the node base digest (Dockerfile and README together) against develop

Every third-party action is pinned to a full commit SHA (with a # vX.Y.Z comment) and every workflow declares least-privilege permissions:. Repository settings, rulesets and the branch workflow are documented in docs/github-setup.md⁠.

⁠workflows/ci.yml

Runs on every pull request into develop or main:

  • Verify source branch — a PR into main must come from develop of this repository.
  • Lint — Hadolint (.hadolint.yaml), ShellCheck, a syntax check of the Python helper, and actionlint.
  • Format — LF line endings, no trailing whitespace, final newline (the rules in .editorconfig that tooling can check reliably).
  • Docs sync — .github/scripts/check-sync.sh fails when README.md and the implementation disagree: a version pinned in Dockerfile.dev that the README does not mention, a Node major that differs between the Dockerfile and the workflows, or an alias missing from (or extra in) the README tables.
  • Build & test — builds the image natively on linux/amd64 and linux/arm64 (no push) and runs .github/scripts/smoke-test.sh: Firebase CLI, pnpm and Starship versions against the pins in Dockerfile.dev (pnpm is resolved with the network disabled, proving the build-time cache works), the container user (UID/GID 1000, passwordless sudo), writable cache directories, Git's trust of /workspace, Node 24, Android platform, Chrome/Chromium, flutter doctor, the shell aliases and the Dev Container metadata label. On amd64 the image is then scanned with Grype; a critical vulnerability that already has a fix fails the check, unless .github/grype.yaml records why it is accepted (only for components that cannot be fixed from this repository, such as the Bouncy Castle library bundled inside Google's Android command-line tools; each entry names the exact version it covers, so it lapses when that component changes). Skipped when a PR touches neither docker/, scripts/, the smoke test nor the CI configuration.
  • npm audit — audits the firebase-tools tree exactly as the image installs it (pinned version plus docker/firebase-tools-overrides.json). Any high or critical advisory fails the check unless .github/npm-audit-allowlist.txt records why it is accepted; moderate and low findings are listed for information. The result and the full report are written to the job summary.
  • CI passed — the single aggregate check required by branch protection. It fails if any job failed or was cancelled; jobs skipped by design count as passed.
⁠workflows/docker.yml

Builds and publishes the multi-platform image (linux/amd64 + linux/arm64) to Docker Hub. Each architecture is built natively on its own runner (no QEMU emulation), smoke-tested with the same script the pull requests use, and only then pushed by digest. Before anything is pushed, a reserve-tag job picks the next free revision R (free on Docker Hub, and creatable as a git tag on GitHub: a name used by an immutable release stays reserved even after the release is deleted, so those numbers are skipped) and creates the git tag flutter-X.Y.Z.R with .github/scripts/reserve-tag.sh. A final job merges both digests into one manifest list, applies the tags and signs the result with a keyless Sigstore⁠ signature (see Verifying the image⁠). Every build carries SBOM and maximum-detail provenance attestations, and the layer cache lives in buildcache-<arch> tags in the registry rather than in the size-limited GitHub Actions cache.

The push trigger is path-filtered (docker/, scripts/, .dockerignore and the workflow itself — everything the build copies or reads), so a README change never rebuilds the image. There is no schedule: the image is published only when an image change is merged to main, or when you run the workflow by hand. Pull requests are validated by ci.yml instead. The jobs use the docker-hub environment, which holds the Docker Hub credentials and is restricted to main.

Note: IMAGE_NAME is hardcoded (not read from a secret) so the image tag is always valid.

⁠workflows/dependency-drift.yml

Dependabot cannot see the tools pinned in Dockerfile.dev, so this workflow compares them with their official release channels every Monday and keeps one issue up to date. It covers firebase-tools, pnpm (within its pinned major line — a newer major is noted separately), Starship, the Android cmdline-tools build, the digest of the node base image, and every advisory accepted in .github/npm-audit-allowlist.txt (reported as soon as a patched release exists). The issue closes itself when every pin is current. Renovate normally opens the PR for firebase-tools, pnpm and the node digest; the issue is the safety net for those and the only prompt for Starship and the cmdline-tools build, whose downloads are checksum-verified and so need a deliberate bump. Bump what it lists as described in Updating the Image⁠.

⁠workflows/dockerhub-description.yml

Syncs README.md to the Docker Hub repository description page on every README.md change merged to main.

⁠workflows/release.yml

A reusable workflow that docker.yml calls once the image is on Docker Hub. The release carries the same name as the image it describes: the release tag, the git tag and the Docker tag are all flutter-X.Y.Z.R (for example flutter-3.47.6.3), and the release is created from the git tag that docker.yml reserved before publishing. The notes show the docker pull command and the digest. A release is created for every push to main that changes the image (same path filter as docker.yml, so a documentation-only promotion produces none) and for the first image of each new Flutter release (for example from a manual run); other rebuilds only add an image revision. Notes are generated from the merged pull requests and grouped by label (.github/release.yml).

⁠workflows/pr-labels.yml

Adds labels to a pull request from its title, so the generated release notes (grouped by label in .github/release.yml) never depend on someone remembering to set them. feat adds feature, fix adds bug, docs adds documentation, ci adds ci, chore/refactor/style/test/perf add chore; the scope deps adds dependencies and docker adds docker; a ! after the type, or a BREAKING CHANGE: line in the description, adds breaking change; and a develop → main promotion gets skip-changelog, because the pull requests it contains are already listed. The workflow only adds labels, so labels set by hand are kept, and a title that is not a Conventional Commit gets a warning instead of a label. It skips bot pull requests (Dependabot and Renovate configure their own labels) and pull requests from forks, which receive a read-only token. The title and description reach the script only through environment variables, and the job has pull-requests: write and nothing else. It is deliberately not part of CI passed: it validates nothing.

⁠workflows/labels.yml

Keeps GitHub repository labels in sync with .github/labels.yml. Labels are version-controlled — add or rename a label in the file, push, and GitHub reflects the change automatically. Run manually to bootstrap on a new r

Tag summary

Content type

Image

Digest

sha256:4bd7f4f74…

Size

2.2 GB

Last updated

14 days ago

docker pull alihaidar199527/flutter-devcontainer