Running Expo/React Native in a Docker container can sometimes cause issues. In this example, I will be running Docker 🐳 within a guest VM (Ubuntu) which will run on my host machine (Windows). My host machine will also be running another VM as the Android emulator (Genymotion) for Expo to connect to. You can get a more detailed post about how to connect two VMs together here, #Plug πŸ”ŒπŸ”ŒπŸ”Œ. Since I’ve set up networking on those two VMs already as far as Expo is concerned it might as well be running on the host machine (Windows). Also in this example, I will be testing this on an Android device.

Original Image: https://maraaverick.rbind.io/2017/11/docker-izing-your-work-in-r/ and https://tutuappapkdownload.com/expo-apk/

Prerequisites

Docker

{
  "main": "node_modules/expo/AppEntry.js",
  "private": true,
  "scripts": {
    "android": "expo-cli start --android",
    "ios": "expo-cli start --ios",
    "start": "expo-cli start"
  },
  "dependencies": {
    "expo": "30.0.0",
    "expo-cli": "2.2.4",
    "react": "16.3.1",
    "react-native": "https://github.com/expo/react-native/archive/sdk-30.0.0.tar.gz"
  }
}

The package.json file I will be using the following example is a very barebones file, just including the minimum packages required to run Expo.

FROM node:latest
LABEL version=1.2.1

ENV ADB_IP="192.168.1.1"
ENV REACT_NATIVE_PACKAGER_HOSTNAME="192.255.255.255"

EXPOSE 19000
EXPOSE 19001

RUN apt-get update && \
    apt-get install android-tools-adb
WORKDIR /app

COPY package.json yarn.lock app.json ./
RUN yarn --network-timeout 100000
CMD adb connect $ADB_IP && \
        yarn run android

FROM node:latest

Tells us which Docker Image we are using as a base, in this case, the official node.js image. This is because it will have a lot of the dependencies we need already installed such as yarn and npm.

ENV ADB_IP="192.168.1.1"
ENV REACT_NATIVE_PACKAGER_HOSTNAME="192.255.255.255"

Sets an environment variable which can be accessed during runtime of the Docker container. Strictly speaking, these don’t need to be here because we can always inject into the Docker container at runtime, but I like to have the environment variables documented.

The ADB_IP is IP of the Android device πŸ“± to connect to. The REACT_NATIVE_PACKAGER_HOSTNAME environment variable is very important because it sets which IP address Expo (cli) is running on, this is the IP Address your phone will try to connect to. If this is not set correctly, you’ll get an error similar to Figure 1. You can work out the correct IP address on Linux by using the following command. The first one should host IP (192.168.27.128 on my machine).

hostname -I
192.168.27.128 192.168.130.128 172.17.0.1 172.19.0.1 172.18.0.1

The reason this environment variable needs to be set is because by default the React Native packager (which expo relies on) picks the first IP it sees on the machine, hence you can run expo on your host machine fine but when you run in a Docker container you cannot connect to it because it’s trying to use the Docker IP address (one of the ones starting with 172.xxx.xxx.xxx).

EXPOSE 19000
EXPOSE 19001

This is essentially meta data letting the user of the Docker container know that they can access data on those ports.

RUN apt-get update && \
    apt-get install android-tools-adb

Install Android Debugging Bridge (ADB), which is used to connect to an Android device and debug the application.

COPY package.json yarn.lock app.json ./
RUN yarn --network-timeout 100000

Copy some important files from host to Docker container. The package.json and yarn.lock are used to install the dependencies and app.json is required by expo as a bare minimum.

CMD adb connect $ADB_IP && \
    yarn run android
    # runs expo-cli start --android

Figure 1: Could not connect error 😒

Running Docker

This command runs when the Docker Image is first to run, every other command is used to build to the image itself. This uses an environment variable passed into the Docker container and connects to the Android device at $ADBIP. Then run the android command in _package.json. Then you can simply run the following commands to build and start your Docker container.

docker build -t expo-android .
docker run -e ADB_IP=192.168.112.101 \
            -e REACT_NATIVE_PACKAGER_HOSTNAME=192.168.1.1 \
            -p 19000:19000 \
            -p 19001:19001 \
            expo-android
  • -t is used to name the image (expo-android)
  • . tells Docker where the Dockerfile is (in the current directory)
  • –env sets environment used by Docker container when it starts to run (REACT_NATIVE_PACKAGER_HOSTNAME andADB_IP are overwritten using these new values)
  • -p publishes ports, in this example, it maps port 19000 on the host to port 19000 on the Docker container (and also 19001), as we need to access port 19000 and 19001 so that Expo (expo-cli) can connect to our Android device.
version: "3.5"

services:
  expo_android:
    container_name: expo_android
    build:
      context: .
      dockerfile: Dockerfile
    env_file: .env
    ports:
      - 19000:19000
      - 19001:19001
    volumes:
      - ${PWD}/:/app/

Since expo is being used to build mobile phone applications Docker isn’t going to be used in production. I prefer to use docker-compose to do the building and running, it means I can run one simple command and do the building and running in one step. Quick aside docker-compose is great for development, especially when you need to run multiple Docker container, but is not really built to be used in production. Look at using a container orchestration tool such as Kubernetes.

I also mount my current directory on the host machine to /app/ directory on the docker container, this is so that any files that change on my host machine will also change in the Docker container, rather than having to build the Docker container again.

docker-compose up --build -d

Environment Variables

ADB_IP="192.168.112.101"
REACT_NATIVE_PACKAGER_HOSTNAME="192.168.27.128"

An example .env file used to pass environment variables (using docker-compose) to the Docker container.

Appendix