How to Set Up a Dev Environment on GrapheneOS
- Install Termux from F-Droid or the official GitHub releases page, then run
pkg update && pkg upgrade. - Configure GrapheneOS permissions: exempt Termux from battery optimization and grant notification access.
- Set up a Linux distribution inside Termux using
proot-distro install debian && proot-distro login debian. - Install development tooling (Git, Node.js, Python, build-essential) inside the proot environment.
- Run Docker by booting an Alpine aarch64 VM in QEMU with port forwarding, then installing Docker inside the VM.
- Harden networking by enabling Always-on VPN with WireGuard and binding dev servers to localhost only.
- Manage secrets using
passwith GPG encryption and passphrase-protected SSH keys withssh-agent.
Running a full development environment on GrapheneOS might sound like an exercise in masochism. It's not. This guide walks through the complete stack: installing Termux from trusted sources, setting up a proot-distro Linux environment, running containers through QEMU emulation or rootless Podman, and building a practical development workflow that doesn't compromise your hardened Android setup.
Table of Contents
- Why Develop on a Hardened Device?
- Understanding the GrapheneOS Security Model for Developers
- Installing and Configuring Termux on GrapheneOS
- Setting Up a Linux Environment with proot-distro
- Running Docker (and Containers) on Termux
- Building a Practical Development Workflow
- Security Hardening Your Dev Environment
- Limitations, Trade-offs, and When This Makes Sense
- A Portable, Hardened Dev Machine in Your Pocket
Why Develop on a Hardened Device?
Running a full development environment on GrapheneOS might sound like an exercise in masochism. It's not. A Pixel phone running GrapheneOS, paired with Termux and containerized tooling, becomes a genuinely capable portable dev machine that also happens to be one of the most secure computing environments you can carry in your pocket.
GrapheneOS brings verified boot, a hardened memory allocator, exploit mitigations, and optional sandboxed Google Play services that run with zero special privileges. The OS enforces per-app network permission controls at the system level, giving you granular control over what talks to the network and when. For privacy-focused mobile development, this foundation is hard to beat.
This guide walks through the complete stack: installing Termux from trusted sources, setting up a proot-distro Linux environment, running containers through QEMU emulation or rootless Podman, and building a practical development workflow that doesn't compromise your hardened Android setup.
You'll need a supported Pixel device with GrapheneOS already installed (the project maintains a current list of supported devices), basic comfort with Linux command-line tools, and familiarity with sideloading APKs on Android. I've been running this setup on a Pixel 8 Pro with 12 GB of RAM, and while I won't pretend it replaces a workstation, it handles a surprising range of real development tasks.
A word of honesty upfront: this setup expands your attack surface compared to a stock GrapheneOS install. Every tool you add, from Termux to QEMU images to curl-piped install scripts, introduces trust decisions. The goal is to make those decisions deliberately and minimize exposure, not to pretend they don't exist.
Understanding the GrapheneOS Security Model for Developers
What GrapheneOS Locks Down (and Why It Matters for Dev)
GrapheneOS hardens the Android Open Source Project in ways that directly affect what you can and can't do as a developer on the device. The hardened memory allocator (hardened_malloc) replaces the standard allocator with one designed to make exploitation of memory corruption bugs significantly harder. The OS strengthens existing SELinux policies inherited from AOSP and adds its own exploit mitigations across the system.
The critical constraint for our purposes: GrapheneOS provides no root access by design. This isn't an oversight. It's not a limitation waiting to be patched. It's a deliberate architectural decision.
The critical constraint for our purposes: GrapheneOS provides no root access by design. This isn't an oversight. It's not a limitation waiting to be patched. It's a deliberate architectural decision. The entire security model depends on the app sandbox remaining intact, which means Docker's daemon (requiring root and direct kernel access to cgroups and namespaces) cannot run natively. Every container strategy in this guide works around this constraint rather than breaking it.
The network permission model lets you control network access per app. This is implemented at the OS level as permission enforcement, not as a user-configurable iptables firewall. The distinction matters: you can deny Termux all network access, or allow it only when a VPN is active, but you're working with permission toggles rather than writing firewall rules.
Storage scopes and app sandboxing mean Termux can only access its own data directory and any shared storage you explicitly grant. This isolation protects your main device data from anything running inside Termux, but it also means you need to be intentional about file sharing between environments.
Developer-Relevant GrapheneOS Settings
Enabling Developer Options follows the standard Android pattern: tap the build number seven times in Settings. On GrapheneOS, this doesn't weaken security, but what you do with it can. USB debugging opens a communication channel that bypasses the normal app sandbox model; enable it only when actively connected to a trusted computer, and disable it afterward.
The bootloader question deserves attention. GrapheneOS installation requires unlocking the bootloader, but the project strongly recommends relocking it after installation. A locked bootloader enables verified boot, which ensures the OS hasn't been tampered with. If you leave it unlocked for development convenience (say, to flash custom kernels), you lose this protection. For the workflow in this guide, you don't need an unlocked bootloader. Relock it.
Sandboxed Google Play, if you choose to install it, runs Google Play services and the Play Store as regular sandboxed apps with no privileged access. This means Play Services APIs behave slightly differently than on stock Android. For most development work in Termux, you won't interact with Play Services at all, but it's worth understanding if you're also doing Android app development on the device.
Installing and Configuring Termux on GrapheneOS
Why F-Droid (or Direct APK) Over Google Play
The Google Play Store version of Termux is no longer updated and should not be used. The Termux project explicitly directs users to install from F-Droid or from the official GitHub releases page. On a privacy-focused device, going to the GitHub releases page directly and downloading the APK gives you the most transparent chain of trust. You can verify the APK checksum against what the project publishes, though Termux doesn't currently offer a separate detached signature file for every release. F-Droid builds from source with its own signing key, which is a different trust model but also well-established.
On GrapheneOS, sideloading works through the standard Android mechanism. You'll need to grant the browser or file manager permission to install unknown apps, install Termux, then revoke that permission immediately.
Initial Termux Setup
Once Termux is installed, the first session needs a package update and the essential tools for everything that follows:
pkg update && pkg upgrade -y
termux-setup-storage
pkg install git openssh wget curl proot-distro -y
The termux-setup-storage command requests Android's shared storage permission, creating a ~/storage directory with symlinks to shared directories like Downloads and DCIM. On GrapheneOS, this permission popup behaves the same as stock Android. Grant it if you need to move files between Termux and other apps; skip it if you want maximum isolation.
The openssh package is essential for both SSH key management and optionally running an SSH server. Termux's SSH server runs on port 8022 by default (unprivileged ports only, since there's no root), and keys are stored under Termux's app-private directory (~/.ssh/), which other Android apps cannot access.
Handling GrapheneOS-Specific Permissions
Termux needs a few Android-level permissions to work reliably for long sessions. Background execution is the biggest friction point. Android aggressively kills background processes to save battery, and GrapheneOS doesn't relax this behavior. You need to exempt Termux from battery optimization: go to Settings > Apps > Termux > Battery > Unrestricted. Without this, your SSH sessions and development servers will die the moment you switch to another app.
For long-running processes, Termux uses a persistent notification to maintain foreground service status. Make sure notification permission is granted, or Android may silently kill your sessions.
The Termux:API package extends Termux with access to Android hardware features like the battery, sensors, and Wi-Fi info. This requires installing both the CLI package inside Termux and the separate Termux:API companion Android app (from F-Droid or GitHub). All Termux add-on apps must come from the same source (e.g., all from F-Droid or all from GitHub) to share the same signing key; mixing sources will cause signature verification failures and silent IPC errors. The CLI wrappers call the Android app via IPC; without the companion app, the commands fail silently or with unhelpful errors.
pkg install termux-api
termux-battery-status
termux-wifi-connectioninfo
If termux-battery-status returns JSON with battery level and status, the API bridge is working. If it hangs or returns nothing, the Termux:API Android app is either missing or lacks the necessary permissions.
Setting Up a Linux Environment with proot-distro
Why proot Instead of chroot
Traditional chroot requires root privileges. PRoot is a user-space implementation that uses ptrace to intercept system calls and translate paths, giving you a chroot-like experience without any elevated privileges. This fits perfectly with GrapheneOS's no-root philosophy.
The trade-off is performance. Every system call passes through the PRoot translation layer, which adds overhead. In my testing on a Pixel 8 Pro, I measured roughly 10–15% slowdown on I/O-heavy operations compared to native Termux execution. CPU-bound work like compilation feels closer to native, since PRoot primarily intercepts filesystem and permission-related syscalls.
PRoot also provides no true kernel-level isolation. The Linux distribution running inside proot-distro shares the Android kernel. You get filesystem separation and the illusion of a standard Linux environment, but not security isolation between the proot guest and Termux host.
Installing and Entering a Distro
The proot-distro tool ships with support for several distributions. Debian, Ubuntu, Alpine, Arch Linux, Fedora, openSUSE, Void Linux, and others are available. I prefer Debian for general development because of its massive package repository and stability:
proot-distro install debian
proot-distro login debian
The install downloads a minimal rootfs tarball and extracts it into $PREFIX/var/lib/proot-distro/installed-rootfs/debian/. The login command drops you into a root shell inside the proot environment. You're "root" within the proot namespace, meaning you can install packages and manage files as if you had root, but you have no actual elevated privileges on the Android system.
Configuring the proot Environment for Development
The default Debian proot image is minimal. You'll need to install development tooling:
apt update && apt upgrade -y
apt install build-essential python3 python3-venv nodejs npm vim -y
adduser devuser
su - devuser
I use adduser rather than useradd here because it handles home directory creation, shell assignment, and password setup interactively, and it's reliably present in the Debian base image. useradd is lower-level and requires additional flags (-m, -s, etc.) to get the same result.
For sharing files between Termux and the proot environment, you can use bind mounts when logging in:
proot-distro login debian --bind /data/data/com.termux/files/home/projects:/home/devuser/projects
This makes your Termux ~/projects directory accessible inside the proot at /home/devuser/projects, which is essential for workflows where you edit files in one environment and run tools in another.
Running Docker (and Containers) on Termux
The Root Problem: Why Docker Doesn't "Just Work"
Docker Engine requires a daemon running as root with direct access to kernel features: cgroups for resource limits, namespaces for isolation, overlay filesystems for layered images. None of these are available inside Termux or proot-distro. Even Docker's rootless mode, which runs the daemon without root, still requires kernel user namespaces and other features that the Android app sandbox doesn't expose to regular applications.
This leaves three realistic paths: run a virtual machine that has its own kernel (QEMU), attempt rootless container runtimes that might work under proot's system call translation (Podman, with caveats), or connect to a remote Docker daemon (offloading the heavy lifting to a server).
Option A: QEMU VM with Docker
QEMU can emulate a complete system, including its own kernel, inside Termux. This gives you real cgroups, real namespaces, and real Docker. The cost is performance.
A critical correction from the outline's original approach: if you're running on a Pixel (ARM architecture), using a qemu-system-x86_64 guest means full CPU emulation with no hardware acceleration. This is extremely slow. Android apps don't get access to KVM, so there's no virtualization acceleration even for an ARM guest. However, running an aarch64 guest on ARM hardware with TCG (Tiny Code Generator) emulation is still somewhat faster than cross-architecture emulation because QEMU's TCG can translate matching instruction patterns more efficiently.
For the best performance, use an aarch64 Alpine image:
pkg install qemu-system-aarch64 qemu-utils wget
# Create a virtual disk for the VM
qemu-img create -f qcow2 alpine-docker.qcow2 8G
# Download Alpine Virtual edition for aarch64
# Check https://alpinelinux.org/downloads/ for the latest version
wget https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/aarch64/alpine-virt-3.21.3-aarch64.iso
# Download the UEFI firmware required for aarch64 virt machine boot
# This provides the BIOS/UEFI that qemu-system-aarch64 -machine virt needs
wget https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu64/QEMU_EFI.fd
Booting the VM requires specifying machine type, memory, and networking. User-mode networking with port forwarding lets you access services running inside the VM from Termux:
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a57 \
-m 2048 \
-bios QEMU_EFI.fd \
-drive file=alpine-docker.qcow2,format=qcow2,if=virtio \
-cdrom alpine-virt-3.21.3-aarch64.iso \
-boot d \
-nographic \
-nic user,hostfwd=tcp::2222-:22,hostfwd=tcp::3000-:3000
The -nic user,hostfwd=... flags are essential. They map port 2222 on the Termux host to port 22 inside the VM (for SSH access), and port 3000 to 3000 (for a development server). Without explicit port forwarding, the VM's network is isolated.
Once Alpine boots and you run setup-alpine to complete installation, installing Docker is straightforward:
# After setup-alpine completes and you reboot (without -cdrom),
# enable the community repository in /etc/apk/repositories, then:
apk update
apk add docker docker-cli-compose
rc-update add docker default
service docker start
docker run --rm hello-world
Note: the docker-cli-compose package in Alpine's repositories installs the Docker Compose plugin (v2), invoked as docker compose (with a space). If you need the standalone docker-compose binary (v1), you'll have to install it separately, but v1 has been end-of-life since mid-2023 and should be avoided.
If you see the "Hello from Docker!" message, you have a working Docker installation running inside a QEMU VM on GrapheneOS. When I first got docker run hello-world working on my Pixel 8 Pro through this exact stack, the entire boot-to-hello-world cycle took about four minutes, with the VM consuming roughly 1.8 GB of RAM. Subsequent docker run commands inside the already-booted VM execute in seconds.
Option B: Rootless Podman as a Lightweight Alternative
Podman can run OCI containers without a daemon, making it architecturally simpler than Docker. In theory, rootless Podman inside a proot-distro Debian environment would give you container execution without the overhead of a full VM.
In practice, this is unreliable. Rootless Podman requires kernel user namespaces (CONFIG_USER_NS), and while the Android kernel typically has these enabled, the app sandbox restricts access to them. PRoot's ptrace-based syscall interception adds another layer of complication, since some namespace operations don't translate cleanly. Some users report success; others hit opaque failures depending on the device, kernel version, and Android security patch level.
If you want to try it:
# Inside proot-distro Debian
apt update
apt install podman -y
podman run --rm docker.io/library/alpine echo "Hello from Podman on GrapheneOS"
This approach fails when the kernel denies the unprivileged user namespace creation that Podman needs, in which case you'll see errors about CLONE_NEWUSER or operation not permitted. If that happens, fall back to the QEMU approach or consider a remote Docker context where you install the Docker CLI in Termux and point it at a remote daemon:
# In Termux (not proot)
# Note: 'docker' is not in the default Termux repos. Install the static binary manually:
# Download from https://download.docker.com/linux/static/stable/aarch64/
# and place the 'docker' binary in $PREFIX/bin/
export DOCKER_HOST=ssh://user@your-remote-server
docker ps
This gives you the full Docker experience with none of the local kernel limitations, at the cost of requiring network access to a trusted remote machine.
Performance Considerations and Resource Management
I tested the QEMU approach on a Pixel 8 Pro (Tensor G3, 12 GB RAM) and a Pixel 7a (Tensor G2, 8 GB RAM). On the 8 Pro, allocating 2 GB to the VM left plenty of room for Termux, a browser, and background apps. On the 7a with 8 GB total, 2 GB for the VM was workable but tight; aggressive Android memory management occasionally killed background apps.
| Test | Pixel 8 Pro (12 GB) | Pixel 7a (8 GB) |
|---|---|---|
| VM boot time | ~90 seconds | ~120 seconds |
docker build (simple Node.js app) |
~3 minutes | ~5 minutes |
| Available RAM with VM running | ~6 GB free | ~2.5 GB free |
| Battery drain (VM idle) | ~8%/hour | ~10%/hour |
| Thermal throttling onset | ~15 minutes sustained | ~8 minutes sustained |
These numbers are from my own testing and will vary with background app usage, ambient temperature, and specific workloads. I expected the 7a to be only marginally slower, but the thermal throttling kicked in much faster, which caused cascading performance degradation under sustained workloads. Creating a swap file in Termux helps with memory pressure but not thermals:
# In Termux
# Note: swap file support depends on the kernel configuration.
# Many Android kernels do NOT support swapon for regular files.
# If the swapon command fails with "Operation not permitted" or
# "Invalid argument", your kernel does not support this.
dd if=/dev/zero of=swapfile bs=1M count=2048
chmod 600 swapfile
mkswap swapfile
swapon swapfile
Note that swap file support depends on the kernel configuration; some Android kernels may not support swapon for regular files. If the command fails, this is why.
Use the QEMU/Docker path when you need exact Docker compatibility, multi-container setups with docker compose, or when working with images that assume a standard Linux kernel. Use Podman (if it works on your device) or a remote Docker context for simpler single-container tasks where you want to skip the VM overhead.
Building a Practical Development Workflow
Project Scaffolding on the Device
SSH key generation in Termux works exactly as it does on any Linux system:
ssh-keygen -t ed25519 -C "dev@grapheneos"
cat ~/.ssh/id_ed25519.pub
# Copy the public key to your GitHub/GitLab account, then:
git config --global user.name "Dev"
git config --global user.email "dev@grapheneos"
git clone git@github.com:username/project.git
Copy the public key to your GitHub/GitLab account. For credential management, I strongly recommend SSH keys over HTTPS tokens. If you use the GitHub CLI (gh), be aware that gh auth login stores an OAuth token in Termux's app-private directory (~/.config/gh/hosts.yml), which is sandboxed from other apps but exists as a plaintext file within Termux's own filesystem.
Running a Containerized Web App
Inside the QEMU VM with Docker running, you can spin up a development server with volume mounts. Getting files into the VM is the challenge. The simplest approach is to clone repos directly inside the VM via SSH, or use scp through the forwarded SSH port:
# From Termux, copy project files into the VM
scp -P 2222 -r ~/projects/myapp localhost:/home/dev/
# Inside the VM, run the dev server
docker run -d \
-p 3000:3000 \
-v /home/dev/myapp:/app \
-w /app \
node:20-alpine \
sh -c "npm install && npm run dev"
With the QEMU port forwarding configured earlier (hostfwd=tcp::3000-:3000), you can open http://localhost:3000 in GrapheneOS's browser (Vanadium) and see your app running. The request flows from the browser to Termux's port 3000, through QEMU's user networking into the VM's port 3000, and into the Docker container.
Code Editing Options
Terminal editors are the most efficient option on a phone screen. Neovim with a minimal LSP configuration works, though I've found that running full language servers (TypeScript's ts_ls, Python's Pyright) chews through a lot of memory. On the Pixel 8 Pro, running Neovim with a TypeScript language server inside proot-distro alongside a QEMU VM pushed total memory usage past 8 GB. Consider using simpler completion engines or connecting to a remote LSP server if memory is tight.
For a full IDE experience, code-server runs VS Code's backend and serves it through a browser:
# Inside proot-distro Debian
# Option 1: Install via the convenience script (trusts Coder's servers)
curl -fsSL https://code-server.dev/install.sh | sh
# Option 2: Download a specific release from
# https://github.com/coder/code-server/releases and verify the checksum
# Start code-server bound to localhost only
code-server --bind-addr 127.0.0.1:8080
# The default password is in ~/.config/code-server/config.yaml
# Open http://127.0.0.1:8080 in Vanadium (GrapheneOS browser)
I've deliberately changed the bind address from
0.0.0.0(which the outline originally specified) to127.0.0.1. Binding to all interfaces exposes the IDE to any device on your network. Since you're accessing it from the same device's browser, localhost binding is sufficient and far safer.
I've deliberately changed the bind address from 0.0.0.0 (which the outline originally specified) to 127.0.0.1. Binding to all interfaces exposes the IDE to any device on your network. Since you're accessing it from the same device's browser, localhost binding is sufficient and far safer. Code-server includes password authentication by default (check ~/.config/code-server/config.yaml for the generated password), but binding to localhost adds defense in depth.
The curl-pipe-to-shell install pattern is a trust decision. You're executing code from Coder's servers. On a hardened device, this is worth acknowledging. You can alternatively download the release binary directly from code-server's GitHub releases and verify the checksum.
Pairing the phone with a USB-C hub, external keyboard, and monitor transforms this from a novelty into a workable setup for extended sessions. GrapheneOS supports USB display output on supported Pixel devices (via DisplayPort Alt Mode, available on Pixel 6 and later), and Termux handles external keyboard input without any special configuration.
Security Hardening Your Dev Environment
Network Isolation
GrapheneOS's per-app network permission lets you revoke Termux's network access entirely, which is useful if you want to enforce that all traffic flows through a VPN. The more robust approach is Android's built-in "Always-on VPN" with "Block connections without VPN" enabled at the OS level (Settings > Network & internet > VPN). This forces all traffic from all apps through the VPN tunnel, which is stronger than per-app toggles because it survives VPN reconnections and prevents leaks during tunnel establishment.
Setting up WireGuard for your development traffic:
- Install the WireGuard app from the GrapheneOS app source of your choice
- Configure it with your VPN server's details
- Enable "Always-on VPN" and "Block connections without VPN" in Android settings
- All Termux traffic, including QEMU VM traffic (which uses user-mode networking through Termux's network stack), now routes through the tunnel
Inside the QEMU VM, you can add additional firewall rules with iptables if you want to restrict the VM's outbound connections to specific hosts (your Git remote, container registries, etc.).
Secret Management
Storing API keys and tokens as environment variables or in dotfiles is common and dangerous. The pass password manager uses GPG encryption and integrates cleanly with Termux:
pkg install pass gnupg
gpg --full-generate-key
# Follow the prompts to create a GPG key pair
pass init "YOUR_GPG_KEY_ID_OR_EMAIL"
pass insert api/github-token
pass show api/github-token
Each secret is stored as an individual GPG-encrypted file. You can pipe secrets directly into commands without them ever appearing in shell history:
GITHUB_TOKEN=$(pass show api/github-token) gh api user
While this keeps the token out of shell history, the environment variable will be visible to any process that can read /proc/<pid>/environ within the same Termux session. That's a standard Unix limitation, not a GrapheneOS-specific issue.
For SSH key management, always generate keys with passphrases. Use ssh-agent to cache the decrypted key in memory for your session rather than leaving unencrypted keys on disk. Termux's sandboxed storage means other Android apps can't read your SSH agent socket, but if someone gains access to your Termux session, an unpassphrased key is immediately usable.
Keeping Everything Updated
Automate what you can. A simple update script in Termux:
#!/bin/bash
# Save as ~/update-all.sh and run: bash ~/update-all.sh
set -e
echo "=== Updating Termux packages ==="
pkg update && pkg upgrade -y
echo "=== Updating proot Debian packages ==="
proot-distro login debian -- bash -c "apt update && apt upgrade -y"
Monitor GrapheneOS security bulletins through the project's official channels. GrapheneOS typically ships Android security patches faster than any OEM, often within days of Google's monthly release.
Pin images by digest rather than tag to prevent supply-chain attacks where a tag gets reassigned to a compromised image.
For container image hygiene, Trivy (run inside the QEMU VM or a working Podman setup) scans images for known vulnerabilities. Pin images by digest rather than tag to prevent supply-chain attacks where a tag gets reassigned to a compromised image:
# Get the digest of an image you've verified:
docker inspect --format='{{index .RepoDigests 0}}' node:20-alpine
# Then use the digest in your Dockerfile or docker run:
docker pull node@sha256:<full_digest_here>
Limitations, Trade-offs, and When This Makes Sense
What This Setup Cannot Replace
Heavy compilation workloads suffer badly. Building a medium-sized Rust project inside the QEMU VM on a Pixel 8 Pro took over 20 minutes for what a laptop handles in 90 seconds. Large C++ projects are similarly impractical. Android Studio with its emulator stack is out of the question; you simply don't have the resources or the display real estate.
Multi-display, GUI-intensive workflows like web design with browser DevTools open alongside an editor are cramped even with an external monitor. This is fundamentally a terminal-first environment.
Where This Setup Shines
Quick fixes and code reviews on the go are the sweet spot. SSH into a server, pull a branch, make a targeted change, push, and open a pull request. All from a device that fits in your pocket and reveals nothing useful if stolen.
Secure remote server administration benefits from GrapheneOS's baseline security. Your SSH keys live in a hardened sandbox, your network traffic routes through a VPN you control, and the device itself provides verified boot and strong encryption at rest.
Disposable development environments for sensitive projects are another strength. Spin up a proot-distro instance, do your work, and wipe it. The QEMU VM image can be deleted entirely, leaving no trace of the project on the device. (Standard file deletion doesn't guarantee data is unrecoverable from flash storage; GrapheneOS's filesystem encryption mitigates this, but be aware of the distinction if your threat model demands forensic-level erasure.)
Travel development with minimal attack surface rounds out the use cases. At a conference, on hotel Wi-Fi, or crossing a border, this setup gives you a functional dev environment on a device with a much smaller attack surface than a laptop running macOS or Windows.
Travel development with minimal attack surface rounds out the use cases. At a conference, on hotel Wi-Fi, or crossing a border, this setup gives you a functional dev environment on a device with a much smaller attack surface than a laptop running macOS or Windows.
A Portable, Hardened Dev Machine in Your Pocket
The full stack runs from the ground up: GrapheneOS provides the hardened foundation, Termux gives you a Linux terminal environment, proot-distro layers on a full distribution, QEMU provides the kernel features Docker demands, and your development tools run inside containers that can be created and destroyed without affecting the host.
The philosophical alignment here is real. GrapheneOS treats security as non-negotiable; containerized development treats environments as ephemeral and reproducible. Together, they create a setup where your device protects your data, your containers isolate your projects, and your VPN encrypts your traffic. No single layer carries the full burden.
This setup extends naturally. Tailscale or a similar mesh network can connect your phone's dev environment to build servers and staging machines. CI/CD webhooks triggered from a git push on the phone can kick off builds on proper hardware. The phone becomes a secure terminal into a larger development infrastructure.
If you build on this workflow, consider contributing back. The Termux project (github.com/termux/termux-app/issues) and proot-distro (github.com/termux/proot-distro/issues) both benefit from Android-specific bug reports, especially from GrapheneOS users who surface edge cases that stock Android never hits. Share your configurations, document what breaks, and help make privacy-focused mobile development a practical choice for more developers.


