Capacitor is the spiritual successor to Cordova and Phonegap. Unfortunately they took this literally and left out actual instructions on how to compile the app to a native android app, because who needs those… At time of writing, Vue recommends either Capacitor or NativeScript, but NativeScript requires prior knowledge about native apps I do not have. If you can work with that, their installation guide is a lot clearer than Capacitor’s is, and possibly a better fit for you.

Credit to the following resources for providing snippets to get this guide working: Install Android SDK CLI Ubuntu 20.04 WSL2 (Work in Progress) (by steveclarke), the documentation of NativeScript and a guide to building a react native app in WSL2 by bergmannjg.

Table of Contents

  1. Prerequisites and goals
  2. Creating a new Vue app and installing Capacitor
  3. Installing the Android SDK
    1. Command line tools
    2. Installing some build tools and platforms
    3. Making our changes permanent
  4. Other dependencies
    1. Gradle
    2. Java JDK
    3. Zipalign
    4. Apksigner
  5. Running a dev build
    1. Make usb devices visible to WSL
    2. Connecting a phone to WSL2
    3. Giving adb permissions to the phone
  6. Building for production
    1. Preparing a key to sign
    2. Assemble and sign your build

Prerequisites and goals

At the end of this blog you will be able to:

Notable things that I will not be touching upon are how to make this work with an emulator, and how to debug the app on a real device.

I expect you have already installed node (I have 16.13.0) and npm (I have 8.1.0). If not, go do that using your favourite method. I also assume you use bash (you can check with ps -p $$). If you do not use bash, there may be slight differences in syntax. I also expect you to have an android device you can connect to via usb, and where you already have enabled the USB debugger.

Creating a new Vue app and installing Capacitor

Added here for completeness sake. Chances are you already pulled this from some other guide.

vue create your-app
cd your-app
npm install @capacitor/core @capacitor/cli
npm install @capacitor/android
npx cap init "My beautiful app" com.something.my_beautiful_app --web-dir=dist
npx cap add android

Installing the Android SDK

After these initial steps the Capacitor documentation stops. You can run an debug build via the following command:

npx cap run android

This will likely yield the error

[error] native-run failed with error

        ERR_SDK_NOT_FOUND: No valid Android SDK root found.

        More details for this error may be available online:

Command line tools

We will tackle this problem one step at a time. First we will install the Android SDK command line tools. Obtain the most up-to-date download link for the command line tools only from the official android studio download page. Alternatively just install the version I used at time of writing (November 2021).

# Install unzip if it is not already installed
sudo apt install unzip

# We will install this in the home directory
cd $HOME
mkdir -p Android/cmdline-tools
unzip -d Android/cmdline-tools
# This location is important, because otherwise you will get an error that the SDK root could not be determined
mv Android/cmdline-tools/cmdline-tools Android/cmdline-tools/latest

# And clean up after ourselves

While we have the command line tools, other tools like Capacitor relying on it do not know where to find it. For this we need to set the ANDROID_HOME environment variable. We also need to update our PATH environment variable so the command line knows where to find the sdkmanager for example.

# These changes last until the end of the current bash session. We will add a more permanent solution later
export ANDROID_HOME=$HOME/Android
export PATH="$ANDROID_HOME/cmdline-tools/latest:$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$PATH"

Installing some build tools and platforms

If you would to run the emulator now (npx cap run android), you’ll notice it at least doesn’t complain that it can’t find an Android SDK root. It still doesn’t work though. To fix this we will run the sdkmanager with some commands. If the sdkmanager complains with an error like Error: Could not determine SDK root. make sure that it is in the correct location. In my version it needs to be in a folder cmdline-tools/latest/bin to automatically detect where the root is.

# Make sure we have the latest information
sdkmanager --update

# We are installing version 31. To figure out what options *you* have, see below
# You'll have to accept licenses by pressing y. Ignore the clause where you sell your soul. That's normal.
sdkmanager "build-tools;31.0.0" "platform-tools" "platforms;android-31" "tools"

If you want to find out what other platforms you can use, you can list the options with sdkmanager --list | grep tools

Last but not least you need to accept any remaining licenses that did not pop up during the initial install.

sdkmanager --licenses

Making our changes permanent

If all of the above went as expected, we can make our changes to our environment variables more permanent. Otherwise figure out what you needed to change, and persist those changes instead.

Edit the ~/.bashrc file with an editor of your choice, then add the following lines to the bottom.

export ANDROID_HOME=$HOME/Android

# Note: I am exporting these from back to front from our previous command
export PATH=$ANDROID_HOME/tools/bin:$PATH
export PATH=$ANDROID_HOME/emulator:$PATH
export PATH=$ANDROID_HOME/platform-tools:$PATH
export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
export PATH=$ANDROID_HOME/cmdline-tools/latest:$PATH

You will need to restart your shell for these changes to take effect. Go do that now. Exit out of the shell (ctrl+d) and start it again. Then test if your path and android home variable are set by echo-ing them in the shell.

# should show a line with something like /home/youruser/Android
# should show a loooong line, with our Android paths at the start
echo $PATH

Other dependencies


Last but not least we need to make sure that gradle is installed. Gradle is used to build our source code into a nice apk that can be loaded on a device.

At time of writing the version of gradle in the Ubuntu repository is woefully out of date, so remove it if it is installed.

sudo apt remove gradle

Then install it from a different repository

sudo add-apt-repository ppa:cwchien/gradle
sudo apt-get update
sudo apt install gradle

# And check if it is higher than v4
gradle -v

Java JDK

You’ll eventually also need the java jdk. You can check if one is already installed for you.

sudo apt list --installed | grep jdk

The easiest way to install the latest working version for you is probably installing default-jdk-headless, which installed openjdk-11-jdk-headless for me.

sudo apt install default-jdk-headless


To eventually be able to create a signed apk you can put in the Google Play Store you will need to have zipalign installed.

sudo apt install zipalign


Similar to zipalign, we will need the apksigner to… sign… the apk… that we want to put in the Google Play Store.

sudo apt install apksigner

Running a dev build

With the android build tools installed, everything will work fine… right? Right?

Unfortunately when running npx cap run android, we quickly figure out that the emulator is not working. We get the following error.

[error] native-run failed with error

        ERR_UNSUITABLE_API_INSTALLATION: No suitable API installation found. Use --sdk-info to reveal missing packages
        and other issues.

        More details for this error may be available online:

The proposed switch doesn’t reveal anything, and the –verbose flag the link suggests doesn’t do anything either. I have not been able to figure out how to get an emulator working on WSL2. You may be able to accomplish this by running adb on Windows instead by following the instructions on bergmannjg’s gist or an article by Adrien Pellegrini showing how to use the Android emulator in Windows 10 with WSL2. What I did figure out is how to make the command deploy a test build to a connected device. Unfortunately this too takes some work.

Make usb devices visible to WSL

The first road block to overcome is to make usb devices visible to WSL2. If you run lsusb in WSL, you will notice that there are no usb devices listed.

To solve this problem we need to create a bridge service that sends usb data from Windows to WSL2. It’s probably best to follow the instructions from the article that introduces it directly: Connecting USB devices to WSL by Ben McMorran. In short: install a service called usbipd-win on Windows, two libraries called linux-tools-5.4.0-77-generic and hwdata on WSL2 and finally add /usr/lib/linux-tools/5.4.0-77-generic to secure_path in the /etc/sudoers file. Finally restart the computer so the service starts.

Connecting a phone to WSL2

This section largely follows what cjshearer commented on a github issue about adb and uses some information in the article linked in the previous paragraph. If anything is unclear, you may want to consult these posts.

First, open Powershell on Windows as administrator. Then open a WSL2 command line window. This makes sure that Ubuntu is actually running. Next plug in your Android test device, and run the following command in Powershell.

usbipd wsl list

In my case there is a whole list of usb devices, but the one I want is marked as

1-6    SAMSUNG Android ADB Interface, SAMSUNG Mobile USB Modem, ...  Not attached

1-6 is the bus id of my phone, so I will bridge it to WSL2 with the following command in Powershell

usbipd wsl attach --busid 1-6

Enter your WSL2 sudo password.

You should now see your device in WSL2 if you execute the lsusb command. Remember that you need to enable this bridge whenever you remove the usb cable from the device and put it back in. It will not remember to bridge the usb (and this is probably for the best).

Giving adb permissions to the phone

When you now do the adb devices command, you’ll notice that it shows you a device number and something like

no permissions (missing udev rules? user is in the plugdev group); see []

So… we have to set up the udev rules for this device. Izzy does a very good job in their answer explaining what to do, so I recommend just following that answer. In short, make note of the device id in the output of lsusb, craft a line with it in a specific rule file, then restart udev and reload the udevadm service, and replug the device. The group you select for this device should be one you are in. Adb suggests plugdev as te group name. You can check your own groups with the groups command.

sudo service udev restart
sudo udevadm control --reload

You may need to do this after every computer restart.

You can now deploy your app for testing purposes to the connected device. Make sure that you authorize the computer on the phone. adb devices should show you the device id followed by “device”. If it shows unauthorized, you still need to authorize it on the phone. If it shows no permissions you may need to restart the services listed above, and you may need to replug your phone.

Building for production

Eventually you probably want to publish your app in the Google Play Store. To do this you will need to generate a signed apk. Since we don’t have a GUI in WSL2, we will have to do this from the command line as well. Use the official documentation on building your app from the command line as a guideline.

Preparing a key to sign

To sign an apk with a key, we first need… a key. This key needs to be constant between releases, so generate it once and make sure to save it to your repository. First, navigate to the android subfolder of your project.

cd /path/to/your/project/android

Then generate a key. We use the alias “release”, since aliases can be enumerated anyway with the keystore’s password so there is no value in making it obscure. Store the password of the keystore securely somewhere outside the repository.

keytool -genkey -v -keystore release.jks -keyalg RSA -keysize 2048 -validity 10000 -alias release

Assemble and sign your build

We first want to assemble an apk that is ready for release, but still needs to be signed. To do this, we just run gradle.

cd /path/to/your/project/android
gradle assembleRelease

Your apk will be generated somewhere deep in the app/build folder. You can quickly get the path with find . -type f -name '*.apk' or hope it is in ./app/build/outputs/apk/release/app-release-unsigned.apk like it is for me.

If you run into problems with an out-of-date version of gradle, scroll up to Other dependencies > Gradle.

Next we zipalign the entire thing:

zipalign -v -p 4 ./app/build/outputs/apk/release/app-release-unsigned.apk ./app/build/outputs/apk/release/app-release-unsigned-aligned.apk

Finally sign the apk. Use the password that you securely stored somewhere several paragraphs ago.

apksigner sign --ks release.jks --out ./app/build/outputs/apk/release/app-release-signed.apk ./app/build/outputs/apk/release/app-release-unsigned-aligned.apk

And finally verify that everything went well.

apksigner verify ./app/build/outputs/apk/release/app-release-signed.apk

Finally copy that file somewhere where you will be able to access it to put it in the Google Play Store.