A few months ago I set myself a goal that, looking back, sounded like technical suicide: fitting an entire PostgreSQL 18 inside an Android app. No external servers, no Docker, no magic containers. I wanted the user to be able to run a CREATE TABLE and that's it, as if they were on their laptop. That idea is where PocketCode came from, and in case anyone's wondering, I'm building it on my own. The road there has been... let's say educational.
The first thing I ran into is that since Android 10 you can't execute anything you write into your own data folder. SELinux cuts you off right away. So I had to play cat and mouse with the security labels. In the end I found that the only place the system lets you run binaries is nativeLibraryDir, where everything shipped inside the APK ends up. Files there are allowed to execute. The fix? Disguise everything as libraries. postgres became libpostgres.so, initdb became libinitdb.so, and the same for the rest of the tools. I put them in jniLibs/arm64-v8a/ and that was it: Android's installer extracts them and gives them permission to run.
But of course, nothing is that easy. When initdb starts, it calls postgres --boot internally, and it looks for the original "postgres" in the directory of its argv[0]. Since I was launching it from the libraries folder, it looked for a file called postgres and only found libpostgres.so. I went half crazy until it occurred to me to create a bin/ folder with symlinks pointing to the .so files. I always launch the tools with that path as argv[0], and since Postgres normalizes paths as text without resolving symlinks, it works (more or less).
At first I used the patches Termux maintains for Postgres on Android. They're really good, but they have one problem: the binary has /data/data/com.termux/... baked in as its install prefix. Postgres uses that to find the timezone data in its share folder. If the user doesn't have Termux (which is the normal case, by the way), the server dies at startup with a weird directory-not-found error. I had to cross-compile it from scratch with my own --prefix. Now the app checks that the binary doesn't point at Termux before it even tries to start it.
Another headache: Android doesn't have shmget(). A stock postmaster tries to use System V shared memory and just hangs without saying anything. I had to force it onto mmap with -c shared_memory_type=mmap -c dynamic_shared_memory_type=mmap, and also preload libandroid-shmem.so for the parts that still ask for the old API. I also found out the binary expects a specific temp folder for the Unix socket and the shared memory layer. If it doesn't exist, the postmaster spins forever. It looked like a deadlock and I was stuck on it for hours. Creating the folder before the first start is enough.
And psql? Well, psql needs readline, which is GPL-3, and my app has a different license. So I left it out of the APK. Instead, the app connects over JDBC (a pgjdbc 42.7.4 I had to patch a bit) to 127.0.0.1:5433, so it doesn't get in the way of any Postgres the user already has on port 5432. Auth is scram-sha-256 with a random password generated the first time and stored encrypted on the device.
The first start is a bit rough: the app verifies the tarball's SHA-256, unpacks it, runs initdb, sets the password and brings the server up. That only happens once. After that the cluster just sits there sleeping. The problem with Android is that it doesn't care whether you're a database or a game: if it runs short on RAM, it kills you without warning. That's why, when the app goes to the background, I force a checkpoint. If it gets killed afterwards, the next start only does a quick recovery instead of replaying an endless WAL.
And of course, Google Play now requires all native code to be aligned to 16 KB pages. Since these are really executables stuffed into .so files, it's very easy to forget to check the alignment on the "libraries". I ended up checking the binaries one by one.
Today PostgreSQL 18.2 runs inside the app on 64-bit ARM phones, with a SQL console, a schema designer and basic monitoring. It doesn't work on x86_64 emulators yet because I don't have binaries built for that architecture. The app is in open testing on the Play Store (search for "PocketCode") if anyone wants to try it. If you have questions about anything, leave a comment and I'll try to answer.
Top comments (0)