← back to blog

how to integrate cloud phones into your CI/CD pipeline in 2026

May 06, 2026

how to integrate cloud phones into your CI/CD pipeline in 2026

if you want to wire cloud phones into your CI/CD pipeline in 2026, the work is mostly plumbing: getting a CI runner to authenticate to a real Android device over the network, install your APK, run a test suite, capture results, and tear down cleanly. cloud phones make this dramatically simpler than maintaining an in-office device lab because every device is reachable over ADB-over-network, and every test run starts from a known-good state.

cloudf.one exposes each phone as an ADB endpoint plus a REST control API. that means GitHub Actions, GitLab CI, Jenkins, CircleCI, and Buildkite can all reach the same fleet without any custom hardware in your office.

why integrate real devices into CI/CD instead of emulators

emulators in CI are tempting because they spin up fast and cost nothing extra. they fail the moment your app uses anything real-device-specific: SafetyNet attestation, Play Integrity, biometric prompts, push notifications via FCM, in-app purchases, ARM-only native libraries, or true network conditions. real cloud phones pass all of these because they are real arm64 hardware on real LTE networks.

if your test suite is purely unit tests or pure UI tests against a stub backend, emulators are fine. once you test real network calls, real push, real fingerprint auth, or real arm64 libraries, you need real devices.

for the broader argument see cloud phone vs physical Android device and real device cloud phones for mobile app testing.

the building blocks: ADB tunnel + REST API + artifact storage

the integration has three layers. ADB tunnel for device control. REST API for fleet management (lock, unlock, reboot, screenshot). artifact storage for test results, screenshots, and video recordings.

cloudf.one provides the first two out of the box. the third you provide via your CI provider’s artifact system (GitHub Actions artifacts, GitLab job artifacts, S3, etc.).

step 1: provision the device pool

before integration, decide on pool size. for a small project, 1 to 2 devices in CI is enough. for parallel test sharding across 4-way splits, you need 4 devices. for full Android version coverage (Android 10 through 15), you need 5 to 8 devices.

cloudf.one lets you tag phones (e.g. ci-pool, android-13, samsung-a52). your CI pipeline picks devices by tag.

step 2: authenticate from your CI runner

cloud phones expose ADB over a TLS-tunneled network endpoint. your CI runner needs the cloudf.one CLI and an API token.

# install cloudf.one CLI in CI
curl -sSL https://cloudf.one/install.sh | bash
export CLOUDFONE_TOKEN=${{ secrets.CLOUDFONE_TOKEN }}
cloudfone auth verify

the token should be stored as a CI secret. never commit it to the repo.

step 3: lock a device and connect ADB

when a CI job starts, it must lock a device so no other job uses it concurrently. when the job ends, it must unlock.

# lock the next available device tagged ci-pool
DEVICE_ID=$(cloudfone device lock --tag ci-pool --duration 30m)
echo "locked device $DEVICE_ID"

# get the ADB endpoint for this device
ADB_ENDPOINT=$(cloudfone device adb-endpoint --id $DEVICE_ID)
adb connect $ADB_ENDPOINT

# verify
adb devices

if no device is available, the lock call blocks for up to a configurable timeout. design CI jobs to retry once on lock-timeout failures.

step 4: install your APK and run tests

with ADB connected, your CI is just running standard Android testing tools.

# install the build artifact
adb -s $ADB_ENDPOINT install -r app/build/outputs/apk/debug/app-debug.apk

# run instrumented tests
./gradlew :app:connectedAndroidTest \
  -PandroidTestSerial=$ADB_ENDPOINT

for Appium, Espresso, or Maestro, the device serial is just $ADB_ENDPOINT and the rest of the tooling is unchanged.

step 5: capture artifacts on failure

when a test fails, capture screenshots, video, and logcat before the runner tears down.

# capture logcat
adb -s $ADB_ENDPOINT logcat -d > artifacts/logcat.txt

# capture a screenshot of current state
adb -s $ADB_ENDPOINT shell screencap /sdcard/fail.png
adb -s $ADB_ENDPOINT pull /sdcard/fail.png artifacts/

# capture a 30s video before teardown
adb -s $ADB_ENDPOINT shell screenrecord --time-limit 30 /sdcard/fail.mp4 &
sleep 31
adb -s $ADB_ENDPOINT pull /sdcard/fail.mp4 artifacts/

upload the artifacts/ folder via your CI provider’s artifact system.

step 6: unlock the device

always unlock at the end of the job, even on failure. use a CI trap or always: block.

# always run on exit
trap "cloudfone device unlock --id $DEVICE_ID" EXIT

a complete GitHub Actions example

name: mobile-e2e
on: [push]
jobs:
  android-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-java@v4
        with:
          java-version: '17'
      - name: install cloudf.one CLI
        run: curl -sSL https://cloudf.one/install.sh | bash
      - name: build APK
        run: ./gradlew assembleDebug assembleAndroidTest
      - name: lock device
        id: lock
        env:
          CLOUDFONE_TOKEN: ${{ secrets.CLOUDFONE_TOKEN }}
        run: |
          DEVICE_ID=$(cloudfone device lock --tag ci-pool --duration 30m)
          echo "device_id=$DEVICE_ID" >> $GITHUB_OUTPUT
          ADB=$(cloudfone device adb-endpoint --id $DEVICE_ID)
          echo "adb_endpoint=$ADB" >> $GITHUB_OUTPUT
          adb connect $ADB
      - name: run tests
        env:
          ADB_ENDPOINT: ${{ steps.lock.outputs.adb_endpoint }}
        run: |
          adb -s $ADB_ENDPOINT install -r app/build/outputs/apk/debug/app-debug.apk
          ./gradlew connectedAndroidTest -PandroidTestSerial=$ADB_ENDPOINT
      - name: capture artifacts on failure
        if: failure()
        env:
          ADB_ENDPOINT: ${{ steps.lock.outputs.adb_endpoint }}
        run: |
          adb -s $ADB_ENDPOINT logcat -d > logcat.txt
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: android-results
          path: |
            logcat.txt
            **/build/reports/androidTests/**
      - name: unlock device
        if: always()
        env:
          CLOUDFONE_TOKEN: ${{ secrets.CLOUDFONE_TOKEN }}
        run: cloudfone device unlock --id ${{ steps.lock.outputs.device_id }}

this pattern works on GitLab CI, Jenkins, CircleCI, and Buildkite with minor syntax changes.

parallel test sharding across multiple devices

for faster CI, shard the test suite across N devices and run them in parallel.

# shard 4 ways
for i in 0 1 2 3; do
  DEVICE_ID=$(cloudfone device lock --tag ci-pool --duration 30m)
  ADB=$(cloudfone device adb-endpoint --id $DEVICE_ID)
  adb connect $ADB
  ./gradlew connectedAndroidTest \
    -PandroidTestSerial=$ADB \
    -Pshard=$i -PshardCount=4 &
done
wait

real device cloud phones from cloudf.one run in parallel without contention because each device is its own physical hardware.

cost control

every locked minute costs money. design pipelines to lock late and unlock fast. lock right before tests start, unlock right after artifacts are captured. do not lock during build or unit-test phases.

a typical 15-minute Android e2e job on 1 device costs roughly the same as 1 hour of cloud phone time at standard pricing. parallelizing 4 ways cuts wall-clock time at 4x device cost; the right tradeoff for a fast feedback loop.

if you want context on infrastructure decisions, our post on cloud phones for QA testing teams covers fleet sizing.

try CI integration on real Singapore devices

register for a free trial and grab a one-hour cloud phone token. that’s enough to run the GitHub Actions example above end to end against your own APK and confirm the integration works. once confirmed, scale via a paid plan and add devices as your CI parallelism grows.

frequently asked questions

what’s the latency from a US CI runner to a Singapore cloud phone?

ADB-over-network adds 150 to 250 ms round-trip from US-East to Singapore. for click-and-verify tests this is fine. for tests that depend on millisecond timing (animations, scroll inertia), run your CI runner in an Asia-Pacific region.

can I use cloud phones with Bitrise or App Center?

yes, any CI that lets you run shell commands and store secrets. Bitrise, App Center, AWS CodeBuild, Travis CI, and self-hosted Jenkins all work. the integration is the same: install CLI, lock device, connect ADB, run tests, unlock.

how do I handle flaky tests that lock devices indefinitely?

set a --duration parameter on the lock call. cloudf.one auto-unlocks the device after the duration expires even if your CI never calls unlock explicitly.

can I run iOS tests this way?

cloudf.one is Android-first. for iOS in CI, look at AWS Device Farm or Firebase Test Lab. our cloudfone vs Firebase Test Lab post covers when each makes sense.

does ADB-over-network work with Espresso, Appium, and Maestro?

yes. all three drivers accept a device serial that can be a network endpoint (adb connect host:port first, then use host:port as the serial). the Appium docs confirm this works for any reachable ADB device.