Setting up a reliable CI/CD pipeline is one of the most impactful upgrades you can make to your Flutter development workflow. Whether you’re deploying to the web, publishing mobile apps, or generating internal builds, automating your build steps ensures higher reliability, repeatability, and team efficiency.
In this guide, we’ll walk through building a CI/CD pipeline for Flutter using Docker, covering:
- Why Docker is helpful for Flutter CI
- Building a custom Flutter Docker image
- Running tests and builds inside Docker
- Setting up CI (example: GitHub Actions)
- Deployment options (Web, APK/AAB, internal delivery)
Why Use Docker for Flutter CI/CD?
Flutter installations vary by OS, and installing the SDK each time in CI can cost minutes or even break builds. Docker provides a stable, predictable environment.
Benefits
- ✔ Consistent environment across local and CI builds
- ✔ Faster builds after initial image pull
- ✔ No need to install Flutter every pipeline run
- ✔ Works for web, Android, Linux, and desktop targets
- ✔ Easy to extend with tools (Firebase CLI, Fastlane, etc.)
Step 1: Create a Custom Docker Image for Flutter
You can use the official Flutter Docker base image, or create your own.
Dockerfile
FROM debian:stable
# Install dependencies
RUN apt-get update && apt-get install -y \
curl git unzip xz-utils zip libglu1-mesa openjdk-17-jdk \
&& rm -rf /var/lib/apt/lists/*
# Install Flutter
RUN git clone https://github.com/flutter/flutter.git /usr/local/flutter -b stable
ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}"
# Pre-download Flutter artifacts
RUN flutter doctor -v
WORKDIR /app
Build and push your Docker image
docker build -t yourdockerhub/flutter-ci .
docker push yourdockerhub/flutter-ci
Step 2: Local Build and Test Using Docker
Run tests
docker run --rm -v $PWD:/app yourdockerhub/flutter-ci flutter test
Build APK
docker run --rm -v $PWD:/app yourdockerhub/flutter-ci flutter build apk
Build Web
docker run --rm -v $PWD:/app yourdockerhub/flutter-ci flutter build web
Step 3: Set Up CI/CD (Example: GitHub Actions)
Here’s a complete GitHub Actions workflow using your Docker image:
.github/workflows/flutter-ci.yml
name: Flutter CI/CD
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Use custom Flutter Docker image
uses: addnab/docker-run-action@v3
with:
image: yourdockerhub/flutter-ci
options: -v ${{ github.workspace }}:/app
run: |
flutter pub get
flutter test
flutter build apk --release
flutter build web
This pipeline:
- Pulls your Docker image
- Installs dependencies
- Runs tests
- Builds APK + Web artifacts
Step 4: Deploying Your Flutter App
(A) Deploy Flutter Web to GitHub Pages
Add this job to your workflow:
deploy_web:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Upload web build
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: build/web
branch: gh-pages
(B) Uploading APK/AAB to Firebase App Distribution
Add Firebase CLI to your Dockerfile:
RUN curl -sL https://firebase.tools | bash
Then use it in GitHub Actions:
- name: Upload APK to Firebase App Distribution
run: |
firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk \
--app ${{ secrets.FIREBASE_APP_ID }} \
--groups "qa-team"
env:
FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
(C) Deploy to Play Store Using Fastlane
Add Fastlane to your image or run it separately:
- name: Deploy AAB to Play Store
run: |
cd android
fastlane deploy
env:
JSON_KEY_FILE: ${{ secrets.GOOGLE_PLAY_JSON }}
PACKAGE_NAME: "com.example.app"
Step 5: Optional – Versioning & Tagging
You can automatically bump versions:
- name: Auto-version
run: |
echo "BUILD_NUMBER=${{ github.run_number }}" >> $GITHUB_ENV
Or update pubspec.yaml before build.
Conclusion
By combining Docker and CI/CD:
- Your Flutter builds become highly reproducible
- You avoid environment inconsistencies
- You simplify onboarding
- Deployment becomes automatic and reliable
This setup works with GitHub Actions, GitLab CI, Bitbucket Pipelines, Jenkins, or any system that can run Docker.
——————-
Here are the detail setup:
✅ A fully working GitLab CI version
✅ Bitbucket Pipelines version
✅ Jenkinsfile
✅ A production-grade Dockerfile
✅ Diagrams for the CI/CD flow
Here, created ready-to-use CI/CD configs and a production-ready Dockerfile for your Flutter + Docker pipeline, plus a Jenkinsfile and a CI/CD flow diagram (Mermaid) you can drop into docs or a README.
Below you’ll find:
- Production-grade
Dockerfilefor Flutter CI (multi-stage, Android SDK, caching, Fastlane & Firebase CLI) gitlab-ci.yml(GitLab CI) — builds/test, produces APK/AAB/web, deploys to Firebase App Distribution & GitLab Pagesbitbucket-pipelines.yml(Bitbucket Pipelines) — similar pipeline using your Docker imageJenkinsfile(declarative Jenkins pipeline) — builds, tests, artifacts, deploy steps- Mermaid diagram for the CI/CD flow + quick notes about secrets and runners
Use these as templates — replace yourdockerhub/flutter-ci, package names, and secret names with your values.
1) Production-ready Dockerfile (multi-stage)
# Stage 1: base with Flutter + Android SDK + tools
FROM ubuntu:24.04 AS base
ENV DEBIAN_FRONTEND=noninteractive \
ANDROID_SDK_ROOT=/opt/android-sdk \
JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \
FLUTTER_HOME=/opt/flutter \
PATH=/opt/flutter/bin:/opt/flutter/bin/cache/dart-sdk/bin:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:${PATH}
# Install basic dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl git unzip xz-utils zip libgtk-3-0 libglu1-mesa \
openjdk-17-jdk wget gnupg2 python3 python3-pip ruby \
&& rm -rf /var/lib/apt/lists/*
# Install Android command line tools and platforms (API 33) and build-tools
RUN mkdir -p ${ANDROID_SDK_ROOT}/cmdline-tools \
&& cd /tmp \
&& curl -sSL "https://dl.google.com/android/repository/commandlinetools-linux-9975646_latest.zip" -o cmdline-tools.zip \
&& unzip cmdline-tools.zip -d ${ANDROID_SDK_ROOT} \
&& mv ${ANDROID_SDK_ROOT}/cmdline-tools ${ANDROID_SDK_ROOT}/cmdline-tools/latest \
&& yes | ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=${ANDROID_SDK_ROOT} --licenses || true \
&& ${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager --sdk_root=${ANDROID_SDK_ROOT} "platform-tools" "platforms;android-33" "build-tools;33.0.2"
# Install Flutter (stable)
RUN git clone --depth 1 --branch stable https://github.com/flutter/flutter.git ${FLUTTER_HOME} \
&& ${FLUTTER_HOME}/bin/flutter doctor -v
# Install fastlane & firebase tools (optional)
RUN gem install fastlane -NV || true
RUN curl -sL https://firebase.tools | bash || true
WORKDIR /workspace
# End of base image
# Stage 2: slimmer build image - copy Flutter + Android SDK from base
FROM ubuntu:24.04 AS flutter-ci
ENV ANDROID_SDK_ROOT=/opt/android-sdk \
FLUTTER_HOME=/opt/flutter \
PATH=/opt/flutter/bin:/opt/flutter/bin/cache/dart-sdk/bin:/opt/android-sdk/cmdline-tools/latest/bin:/opt/android-sdk/platform-tools:${PATH}
# Copy installed tools from base stage
COPY --from=base /opt /opt
COPY --from=base /usr/lib/jvm /usr/lib/jvm
COPY --from=base /usr/local/bundle /usr/local/bundle || true
# Install lightweight extras
RUN apt-get update && apt-get install -y --no-install-recommends \
gettext-base ca-certificates curl git unzip xz-utils zip libglu1-mesa \
&& rm -rf /var/lib/apt/lists/*
# Accept Android licenses (at image build time)
RUN yes | flutter doctor --android-licenses || true
# Create a non-root user for better security
RUN useradd -m -u 1000 builder
USER builder
WORKDIR /home/builder/app
# Cache directories (helpful when mounting volumes during CI runs)
VOLUME ["/home/builder/.pub-cache","/home/builder/.gradle","/home/builder/.android"]
# Entrypoint to show flutter version by default (override at docker run)
ENTRYPOINT ["flutter"]
CMD ["--version"]
Notes
- This image contains Flutter + Android SDK + Fastlane + Firebase CLI. Build smaller images by removing optional tools.
- Use image caching in your registry to speed up CI.
- The Dockerfile accepts Android licenses at build time; in CI you still may need to set
ANDROID_HOME/ANDROID_SDK_ROOTenvs and mount gradle cache.
2) gitlab-ci.yml (GitLab CI)
image: yourdockerhub/flutter-ci:latest
stages:
- prepare
- test
- build
- deploy
variables:
FLUTTER_CHANNEL: "stable"
ANDROID_SDK_ROOT: "/opt/android-sdk"
# ensure gradle does not ask interactive questions
GRADLE_USER_HOME: "${CI_PROJECT_DIR}/.gradle"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .pub-cache/
- .gradle/
- build/
before_script:
- flutter --version
- flutter pub get
prepare:
stage: prepare
script:
- echo "Preparing build..."
artifacts:
paths:
- .pub-cache/
- .gradle/
test:
stage: test
script:
- flutter test --coverage
artifacts:
reports:
junit: junit-report.xml
paths:
- build/
when: always
build_android:
stage: build
script:
- flutter build apk --release -v
- flutter build appbundle --release
artifacts:
paths:
- build/app/outputs/**/*.apk
- build/app/outputs/**/*.aab
expire_in: 1 week
build_web:
stage: build
script:
- flutter build web --release
artifacts:
paths:
- build/web
expire_in: 1 week
deploy_firebase:
stage: deploy
needs: ["build_android"]
only:
- main
script:
- echo $FIREBASE_TOKEN > firebase_token.json
- firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk --app $FIREBASE_APP_ID --groups "qa" --token $FIREBASE_TOKEN
environment:
name: staging
when: manual
allow_failure: false
pages:
stage: deploy
dependencies:
- build_web
script:
- mv build/web public
artifacts:
paths:
- public
only:
- main
Secrets to set in GitLab CI (Project → Settings → CI/CD → Variables)
FIREBASE_TOKEN(for Firebase CLI)FIREBASE_APP_IDGOOGLE_PLAY_JSON(if using Fastlane & Play Store)KEYSTORE_*envs if signing APK/AAB (or store file in protected artifacts)
3) bitbucket-pipelines.yml (Bitbucket Pipelines)
image: yourdockerhub/flutter-ci:latest
pipelines:
default:
- step:
name: Install & Test
caches:
- docker
- gradle
script:
- flutter --version
- flutter pub get
- flutter test
- step:
name: Build Android
script:
- flutter build apk --release
- pipe: atlassian/upload-artifact:0.2.3
variables:
ARTIFACT_PATH: 'build/app/outputs/flutter-apk/app-release.apk'
- step:
name: Build Web
script:
- flutter build web --release
- pipe: atlassian/upload-artifact:0.2.3
variables:
ARTIFACT_PATH: 'build/web/**'
branches:
main:
- step:
name: Deploy to Firebase (App Distribution)
script:
- pipe: atlassian/git:0.9.1
- echo $FIREBASE_TOKEN > firebase_token.json
- firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk --app $FIREBASE_APP_ID --groups "qa" --token $FIREBASE_TOKEN
Bitbucket variables to set
FIREBASE_TOKENFIREBASE_APP_IDGOOGLE_PLAY_JSON(as secure file or environment variable)
4) Jenkinsfile (Declarative pipeline)
pipeline {
agent { docker { image 'yourdockerhub/flutter-ci:latest'; args '-u root:root' } }
environment {
ANDROID_SDK_ROOT = "/opt/android-sdk"
GRADLE_USER_HOME = "${env.WORKSPACE}/.gradle"
}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
timestamps()
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Dependencies') {
steps {
sh 'flutter --version'
sh 'flutter pub get'
}
}
stage('Test') {
steps {
sh 'flutter test --coverage'
junit allowEmptyResults: true, testResults: '**/test-*.xml'
}
}
stage('Build APK/AAB') {
steps {
sh 'flutter build apk --release'
sh 'flutter build appbundle --release'
}
post {
success {
archiveArtifacts artifacts: 'build/app/outputs/**/*.apk, build/app/outputs/**/*.aab', fingerprint: true
}
}
}
stage('Build Web') {
steps {
sh 'flutter build web --release'
}
post {
success {
archiveArtifacts artifacts: 'build/web/**', fingerprint: true
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
script {
// Example: Upload to Firebase App Distribution using token in Jenkins credentials
withCredentials([string(credentialsId: 'FIREBASE_TOKEN_ID', variable: 'FIREBASE_TOKEN')]) {
sh 'firebase appdistribution:distribute build/app/outputs/flutter-apk/app-release.apk --app $FIREBASE_APP_ID --groups "qa" --token $FIREBASE_TOKEN'
}
}
}
}
}
post {
always {
archiveArtifacts artifacts: 'build/**', allowEmptyArchive: true
}
failure {
mail to: '[email protected]', subject: "Build failed: ${env.JOB_NAME} #${env.BUILD_NUMBER}", body: "Check Jenkins"
}
}
}
Jenkins notes
- Add credential
FIREBASE_TOKEN_ID(Secret text) andGOOGLE_PLAY_JSON(secret file) if needed. - Run the Jenkins agent with Docker support and privileged mode if you plan to launch emulators.
5) CI/CD Flow Diagram (Mermaid)
You can paste this into Markdown that supports Mermaid or into a mermaid.live editor.
flowchart LR
A[Developer push to repo] --> B{CI Trigger}
B --> |push/main| C[CI Runner pulls image yourdockerhub/flutter-ci]
C --> D[flutter pub get]
D --> E[Run tests]
E --> F{tests ok?}
F -->|no| G[Fail & notify]
F -->|yes| H[Build artifacts]
H --> H1[Android APK / AAB]
H --> H2[Web build]
H1 --> I[Sign (keystore / fastlane)]
I --> J[Upload to Firebase App Distribution / Play Store]
H2 --> K[Deploy to GitLab Pages / GitHub Pages / CDN]
J --> L[QA + Release]
K --> L
G --> M[Open issue / notify via Slack]
Quick Implementation Checklist
- Build and push Docker image (
docker build -t yourdockerhub/flutter-ci . && docker push ...) - Add CI variables/secrets:
FIREBASE_TOKEN,FIREBASE_APP_IDGOOGLE_PLAY_JSON(service account JSON for Play Store)- Keystore and signing configs (
KEYSTORE_PASSWORD,KEY_ALIAS,KEY_PASSWORD) or use Fastlane match
- Ensure CI runners allow Docker images that contain Android SDK (some shared runners may not allow privileged operations)
- Configure artifact retention and caching (
.pub-cache,.gradle) to speed up builds - (Optional) Protect deploy jobs to run only on
mainand require manual approvals for production


Leave a Reply