Step-by-step guide to get up and running with avdctl in Docker.
# Build and start in background
docker-compose up -d --build
# Watch build progress (if needed)
docker-compose logs -f# Enter container shell
docker-compose exec avdctl bash
# Inside container: Create base AVD
avdctl init-base --name base-a35
# This will:
# - Download Android 35 system image with Play Store (~1-2 GB)
# - Create base AVD in /root/.android/avd/base-a35.avd
# - Takes 5-10 minutes depending on internet speed
# Exit container when done
exit# Get the volume mount point
docker volume inspect avdctl_avd-home --format '{{ .Mountpoint }}'
# Example output: /var/lib/docker/volumes/avdctl_avd-home/_data
# List AVDs (requires sudo)
sudo ls -la /var/lib/docker/volumes/avdctl_avd-home/_data/You should see:
base-a35.avd/- Directory with AVD filesbase-a35.ini- AVD configuration
Option A: Use Host Emulator Directly on Docker Volume
# Set AVD home to Docker volume
export ANDROID_AVD_HOME=/var/lib/docker/volumes/avdctl_avd-home/_data
# Launch emulator with GUI (requires Android SDK on host)
emulator -avd base-a35 -no-snapshot
# Or with writable mode (needed for first boot)
sudo emulator -avd base-a35 -no-snapshotOption B: Copy AVD to Host, Configure, Copy Back
# Copy AVD to your host AVD directory
sudo cp -r /var/lib/docker/volumes/avdctl_avd-home/_data/base-a35.* ~/.android/avd/
# Fix permissions
sudo chown -R $USER:$USER ~/.android/avd/base-a35.*
# Launch emulator normally
emulator -avd base-a35 -no-snapshot✅ Skip to start, then go to Settings:
-
Google Account
- Settings → Accounts → Add account → Google
- Sign in with your test Google account
- This enables Play Store
-
Fingerprint (optional)
- Settings → Security → Fingerprint
- Use emulator's "Touch sensor" button (sidebar)
- Enroll at least one fingerprint
-
Developer Options
- Settings → About phone → Tap "Build number" 7 times
- Settings → System → Developer options
- Enable "Stay awake"
- Set all animation scales to 0.5x or off
-
Install Base Apps (optional)
- Open Play Store
- Install any apps you want in all clones
- Wait for installations to complete
-
Let It Settle
- Leave emulator idle for 30-60 seconds
- Let background processes finish
-
Close Cleanly
# From another terminal adb emu kill
If using Option B, copy back:
# Copy configured AVD back to Docker volume
sudo cp -r ~/.android/avd/base-a35.* /var/lib/docker/volumes/avdctl_avd-home/_data/# Enter container again
docker-compose exec avdctl bash
# Save the configured AVD as golden image
avdctl save-golden --name base-a35 \
--dest /avd-golden/base-a35-configured.qcow2
# Verify
ls -lh /avd-golden/
# Should show base-a35-configured.qcow2 (500MB-2GB)
exit# Enter container
docker-compose exec avdctl bash
# Create clones (fast - just creates thin overlays)
avdctl clone --base base-a35 --name w-customer1 \
--golden /avd-golden/base-a35-configured.qcow2
avdctl clone --base base-a35 --name w-customer2 \
--golden /avd-golden/base-a35-configured.qcow2
avdctl clone --base base-a35 --name w-customer3 \
--golden /avd-golden/base-a35-configured.qcow2
# List clones
avdctl list# Still inside container
# Start multiple instances (background)
avdctl run --name w-customer1 --port 5580 &
avdctl run --name w-customer2 --port 5582 &
avdctl run --name w-customer3 --port 5584 &
# Wait for boot
sleep 30
# Check status
avdctl ps
# Should show all 3 running# On host (new terminal)
adb devices
# Should see:
# emulator-5580 device
# emulator-5582 device
# emulator-5584 device
# Run commands
adb -s emulator-5580 shell getprop ro.build.version.release
adb -s emulator-5582 shell pm list packages | grep google
adb -s emulator-5584 shell dumpsys window | grep mCurrentFocus
# Install APK to specific emulator
adb -s emulator-5580 install myapp.apk# Inside container
avdctl stop --name w-customer1
avdctl stop --name w-customer2
avdctl stop --name w-customer3
# Verify all stopped
avdctl ps
# Should show: (no emulators)# Start
docker-compose up -d
# Stop
docker-compose down
# Logs
docker-compose logs -f
# Enter shell
docker-compose exec avdctl bash
# Restart
docker-compose restart# List AVDs
avdctl list
avdctl list --json
# List running
avdctl ps
avdctl ps --json
# Stop instance
avdctl stop --name w-customer1
avdctl stop --serial emulator-5580
# Delete clone
avdctl delete w-customer1# AVD location
docker volume inspect avdctl_avd-home --format '{{ .Mountpoint }}'
# Golden images location
docker volume inspect avdctl_avd-golden --format '{{ .Mountpoint }}'
# List AVDs
sudo ls -la $(docker volume inspect avdctl_avd-home --format '{{ .Mountpoint }}')
# List golden images
sudo ls -lh $(docker volume inspect avdctl_avd-golden --format '{{ .Mountpoint }}')If you used Option A (direct access to volume) and got permission errors:
# Fix permissions in container
docker-compose exec avdctl bash
chown -R root:root /root/.android/avd
exit# Check KVM on host
ls -l /dev/kvm
# Fix permissions
sudo chmod 666 /dev/kvm
# Restart container
docker-compose restart# The container exposes ports 5554-5586
# But adb needs to connect explicitly
# Inside container, check what's running
docker-compose exec avdctl avdctl ps
# On host, connect to those ports
adb connect localhost:5555 # for emulator-5554
adb connect localhost:5557 # for emulator-5556
adb connect localhost:5581 # for emulator-5580# Compress more aggressively (inside container)
qemu-img convert -O qcow2 -c -o compression_type=zstd \
/avd-golden/base-a35-configured.qcow2 \
/avd-golden/base-a35-compressed.qcow2
# Use compressed version for clones
avdctl clone --base base-a35 --name w-test \
--golden /avd-golden/base-a35-compressed.qcow2# Stop and remove container
docker-compose down
# Delete volumes (WARNING: destroys all AVDs and golden images!)
docker volume rm avdctl_avd-home avdctl_avd-golden
# Start fresh
docker-compose up -d --buildIf you don't have Android SDK on host or don't want to use GUI:
# Inside container, use prewarm
docker-compose exec avdctl bash
# Create base
avdctl init-base --name base-a35
# Auto-boot and save (no manual config)
avdctl prewarm --name base-a35 \
--dest /avd-golden/base-a35-prewarmed.qcow2 \
--extra 45s \
--timeout 5m
# This creates a clean, booted golden image
# BUT: No Google account, no Play Store login, no fingerprintNote: Without GUI configuration, clones won't have Google account or Play Store access. Good for testing apps that don't need Google services.
- Read DOCKER.md for advanced usage (baking APKs, backups, CI/CD)
- Read README.md for native (non-Docker) usage
- Read CRUSH.md for development details
1. docker-compose up -d --build # Start container
2. docker-compose exec avdctl bash # Enter container
3. avdctl init-base --name base-a35 # Create base
4. exit # Exit container
5. sudo emulator -avd base-a35 ... # Configure with GUI on host
(or copy to ~/.android/avd, configure, copy back)
6. docker-compose exec avdctl bash # Enter container again
7. avdctl save-golden --name base-a35 ... # Save golden
8. avdctl clone ... (repeat for N customers)
9. avdctl run --name w-customer1 ... & # Run in parallel
10. avdctl ps # Monitor
Enjoy! 🚀