Building React Native in Docker

At In the Pocket, we recently got a lot more ownership over the app-building process for our team’s customers.

Previously, we shared our toolset with all teams. We used Ansible to provision our team-mac server. The server was running our Jenkins instance as well as building our iOS apps. Additionally, we shared a docker image with the company to build Android apps on the Google Cloud.

This dependency of shared tools led to 2 big problems. Firstly, it wasn’t always clear who was maintaining what. If a team wanted to upgrade to the latest Android SDK or a newer XCode version, this would have implications for everyone. Secondly, the docker image was bloated by its ability to build React Native, Flutter and native Android apps. It also contained SDK 27, 28 & 29. Simply put: more SDKs and tools meant a slower docker container.

In this situation, every now and then we had building issues that weren’t always easy to pinpoint or fix. Or we needed help from colleagues that weren’t available for immediate action.

Not so long ago, we moved to our private Gitlab and took the opportunity to use Gitlab CI and polish our toolset! This blog post focuses on what we learned about Docker and building for Android.

Docker-Android?

As we were investing time to migrate our continuous integration from Jenkins to Gitlab CI, we found out there is actually a Docker container maintained by React Native Community: Docker-Android 🥳!

We happily crafted our .gitlab-ci.yml file, only to find out that bundler (from Ruby) wasn’t pre-installed 🤕. A first ‘quick fix’ was installing it on every run. Unfortunately, it was a waste of time and only slowed down every build. At that point, we really started digging into the matter!

How to test?

Changing your .gitlab-ci.yml file, commiting & pushing changes is a slow process to see if your app is building correctly in docker.

So what can you do? You can actually build, run & log into the docker container quite easily! Once that’s over, you can set up git and checkout your project, or just copy it from your pc.

Start a docker container

First you have to build your Dockerfile:

docker build — tag itporbit/react-native-android:latest .

Now we can start it. The option — ti will make it interactive and — rm will stop the container once you exit the terminal.

docker run --rm -ti itporbit/react-native-android

Checkout, install & build

Now you are logged in the docker container you can do a git checkout, npm install & build your app!

git clone -b develop --singe-branch https://github.com/repo.gitcd app/npm cicd android./gradlew assembleRelease

Our own Dockerfile ❤️

At this point we have 2 options.

We could fork the docker-android project, and adapt the dockerfile to our needs. That means we would have to merge upstream changes when they occur.

Instead, we opted to create our own Dockerfile and extend from reactnativecommunity/react-native-android:2.1. This way we don’t depend on another git repo. We are in control of which version we extend and when we upgrade to a new release.

Now we are ready to make some changes!

Add support for Fastlane

Our first Dockerfile set some locale stuff and installed bundler.

FROM reactnativecommunity/react-native-android:2.1# set locale to utf8 for fastlaneENV LANG C.UTF-8ENV LC_ALL C.UTF-8# install bundlerRUN gem install bundler

As we use Fastlane, we don’t use ./gradlew but:

bundle installbundle exec fastlane build_android

Non-root user

The react-native-android container runs with a root user, which comes in handy to extend from the container but poses a security risk. Therefore, we added a user (reactnative) and switched the workdir to home/reactnative.

RUN useradd -ms /bin/bash reactnative# transfer ownership of $ANDROID_HOME (opt/android) so our new user can install additional sdk or build tools at build time. This is needed if you use libraries that target other Android SDK's.RUN chown reactnative:reactnative $ANDROID_HOME -RUSER reactnativeWORKDIR /home/reactnative

Versioning is important!

Years ago, when we were still building apps on a self-managed build server, we definitely lost the ability to build older apps when we upgraded the software. By creating our own react-native-android docker container, every version is tailor-made for building a specific Android version. Version 1 builds React Native apps that target Android 10 (SDK 29). When we upgrade React Native to a new version that targets Android 11, we will update release version 2, made for building Android 11 (SDK 30) apps!

Thanks to this, we always target a specific version and not the latest tag in our .gitlab-ci file. When we have to do a hotfix, we will always build our app in the correct container 🎉.

Make

We use a simple make task so you don’t have to remember or document the docker commands to build, publish or run our container.

all: buildVERSION = 1.1build:  docker build --tag itporbit/react-native-android:${VERSION} .push: build  docker push itporbit/react-native-android:${VERSION}  git tag react-native-android/${VERSION} HEAD  git push --tagsrun: build  docker run --rm -ti itporbit/react-native-android:${VERSION}

So we can now invoke make run to test stuff out after making changes to our Dockerfile!

Resources and failing builds on mac

While testing, every now and then my docker image started failing unexplainably while building. Apt could no longer install dependencies for example. This wasn’t the case, but my reserved disk space was depleted. An easy fix, by running docker builder prune or docker image prune in the terminal so that caching of past builds was removed.

Interesting reads

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store