Building Nativescript apps on Gitlab CI

I’m building a Passit Android app through Gitlab CI. My goal is to produce a signed apk file in passit-mobile’s public Gitlab repo without exposing my signing key. This post will outline what I did and act as a starting point to anyone wishing to do the same. It should also be relevant for anyone using React Native or another Docker based CI solution.

Screenshot from 2018-01-27 18-31-52

At a high level: In order to generate the APK file automatically in Gitlab CI, I need to build my project in Docker. That means I need the Android SDK and the Node environment for Nativescript in Docker. I also need to get my key file and password to the CI runner in a secure manner. Finally, I need to place the file somewhere where it can be downloaded.

Here’s my complete .gitlab-ci.yml file.

Local Docker

Local docker isn’t CI! OK, but we need to run everything locally inside Docker first to make sure it works. It’s no fun waiting on slow CI when debugging. I’ll create a Dockerfile for my project. I found runmymind/docker-android-sdk:ubuntu-standalone to be the most popular and still-updated Android SDK Docker image at the time of this writing. However, the image doesn’t have node installed, since Android is Java-based. So my Dockerfile is going to install node, Nativescript, other packages, and the Android platform version I want. Here are the relevant lines:

# Installs Node.js
ENV NODE_VERSION 8.9.4
RUN cd && \
 wget -q http://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz && \
 tar -xzf node-v${NODE_VERSION}-linux-x64.tar.gz && \
 mv node-v${NODE_VERSION}-linux-x64 /opt/node && \
 rm node-v${NODE_VERSION}-linux-x64.tar.gz
ENV PATH ${PATH}:/opt/node/bin

# Installs nativescript
RUN npm install -g nativescript --unsafe-perm
RUN npm install nativescript --unsafe-perm
# installs android platform
RUN $ANDROID_HOME/tools/bin/sdkmanager "tools" "platform-tools" "platforms;android-25" "build-tools;25.0.2" "extras;android;m2repository" "extras;google;m2repository"

Neat. Notice the –unsafe-perm in the Nativescript install. That’s right from the Nativescript advanced install steps. At this time platform version (25) works with Nativescript, but you may need to change it.

I also made a tiny docker compose file. I probably don’t need to but I like it’s easy to remember cli syntax. To build and run the docker image:

  1. Install Docker if you haven’t already
  2. docker-compose build
  3. docker-compose run –rm android bash

The last step is to get a bash terminal inside the container. I suggest trying a tns command to see if it works – it may not connect to any emulators nor devices, but it should be able to build.

Building

Before continuing, I suggest you read https://docs.nativescript.org/publishing/publishing-android-apps if you haven’t already.

Generally I build with webpack, aot, and snapshot but not uglify (can’t get it to work yet). This looks like:

tns build android --release --bundle --env.snapshot --env.aot --key-store-path your-key.jks --key-store-password <your-password> --key-store-alias-password <your-password> --key-store-alias your-alias

I’d suggest ensuring this command works outside of Docker first, then within your Docker container, and finally on CI. The reason to use all of this and not just tns build Android is to make app performance and startup better. Nativescript + Angular is quite slow otherwise. If you aren’t using Angular, it will be a little different but you can still use snapshot.

Gitlab CI

At this point you have verified that you can build an APK file inside of Docker. Next up is the .gitlab-ci.yml file. I basically just mimic my local Docker file. You could actually use the same image if you wanted and use Docker in Docker. It seems a little simpler to me to keep them separate. There are a few extra commands I’ll call out.

echo $ANDROID_KEY_BASE64 | base64 -d > key.jks

I need to get my key file into gitlab CI without committing to the public repo. I can use Gitlab CI’s protected secret variables for this. Unfortunately, Gitlab doesn’t support private files at this time. But I can base64 encode bytes – and what is a file if not a series of bytes?

cat your-key.jks | base64 -w 0

This will convert your key file to base64 without newlines (which will be more gitlab CI variable friendly). Now paste this base64 into a secret and protected variable. I called mine ANDROID_KEY_BASE64. I also set my password for the key to ANDROID_KEY_PASSWORD (doesn’t need to be base64). Now I can regenerate my key file on the fly in the CI runner. Protected secret variables will only show up in a protected branch – and I can limit who has access to protected branches. So no one can get my info by, say, submitting a merge request with echo $ANDROID_KEY_PASSWORD.

Even if your repo is private, it’s not a bad idea to keep secrets hidden so that others can contribute without seeing them.

The last thing I’ll note is my artifacts section, which will store my APK file after each build. This is great for daily build users or if you want to just manually upload the APK to Google Play and other stores. Ensuring the app builds with production settings is also a fantastic test in itself.

Final Thoughts

That’s it – fully automated APK builds. This method will not work for iOS, as Apple does not allow you to build iOS apps in non-Apple operating systems such as Linux. And Docker runs in Linux.

(Side rant: Screw you, Apple! I sure hope the U.S. gets an administration that cares about anti-trust again – then maybe your development process wouldn’t be two decades in the past. Why would you make a human build something when it could just be automated?)

Improvements and/or the next levels of this process:

  • Running unit and integration tests. Unit testing comes with nativescript and integration testing could be done with Appium. You can actually run an Android emulator in Docker.
  • Automating the deploy based on branch or tags to publish to any stores.
  • It would be great if someone made a Nativescript app-building Docker image – that would let me simplify many steps here. I could imagine even tagging each supported Android platform version so the only steps to do per project would be sending over the key file, npm install, and tns build.
  • You could try iOS builds by hooking up a physical Apple computer to gitlab CI – you can keep it in your garage right next to your horse and buggy!

Feel free to comment if any of these steps aren’t working right!

By David

I am a supporter of free software and run Burke Software and Consulting LLC. I am always looking for contract work especially for non-profits and open source projects. Open Source Contributions I maintain a number of Django related projects including GlitchTip, Passit, and django-report-builder. You can view my work on gitlab. Academic papers Incorporating Gaming in Software Engineering Projects: Case of RMU Monopoly in the Journal of Systemics, Cybernetics and Informatics (2008)

Leave a comment