Sandbox your React Native development

Expo CLI is a tool to install and run React Native applications. Expo is a toolchain that claims to be “the fastest way to build an app”.

You can install those tools on your local machine. But maybe you want to create a stand-alone development environment using Docker containers.

That way you can be sure of a consistent development environment across different machines.

In this post I’ll show you how to create a docker container that will allow you to develop React Native applications.


I assume that you have Docker Desktop and docker-compose installed on your machine.

If not, head over to the official website.

Instructions for my favorite OS, Arch Linux (or rather Manjaro Linux) are available on the wiki.

You’ll also need Node.js.

Expo CLI

Let’s first make a parent folder that will hold our project. As I am on Linux, the following command will do the trick:

mkdir docker-react-native && cd docker-react-native

Then we’ll initialize the project on our local machine:

npx expo-cli init react_native_app --npm

This command will create a new folder and install all required dependencies using npm. If you rather use yarn, you can leave out the --npm flag.


The Docker setup relies on Bret Fisher’s node-docker-good-defaults.

Create the following Dockerfile inside the main folder. In our example, it’s called docker-react-native.

# pull base image
FROM node:14.13.1-buster-slim

# set our node environment, either development or production
# defaults to production, compose overrides this to development on build and run
ARG NODE_ENV=production

# default to port 19006 for node, and 19001 and 19002 (tests) for debug
ARG PORT=19006
EXPOSE $PORT 19001 19002

# install global packages
ENV NPM_CONFIG_PREFIX=/home/node/.npm-global
ENV PATH /home/node/.npm-global/bin:$PATH
RUN npm i --unsafe-perm -g npm@latest expo-cli@latest

# install dependencies first, in a different location for easier app bind mounting for local development
# due to default /opt permissions we have to create the dir with root and change perms
RUN mkdir /opt/react_native_app && chown node:node /opt/react_native_app
WORKDIR /opt/react_native_app
ENV PATH /opt/react_native_app/.bin:$PATH
USER node
COPY ./react_native_app/package.json ./react_native_app/package-lock.json ./
RUN npm install

# copy in our source code last, as it changes the most
WORKDIR /opt/react_native_app/app
# for development, we bind mount volumes; comment out for production
# COPY ./react_native_app .

ENTRYPOINT ["npm", "run"]
CMD ["web"]

Let’s build the docker container!
Inside the docker-react-native folder (where the Dockerfile is):

docker build -t react_native_app .

Now let’s try running it:

docker run -it --rm --name react_native_app \
           -p 19006:19006 \
           -v (pwd):/opt/react_native_app/app:delegated \
           -v notused:/opt/react_native_app/app/node_modules \

Let’s break it down:

  • docker run: starts th docker container
  • -it: interactive mode with tty - so that we can kill the docker container easily with CTRL+C
  • --rm: remove the container after stopping it
  • --name: give the thing a name!
  • -p 19006:19006: binds the port 19006 inside the docker container to localhost:19006
  • -v (pwd):/opt/react_native_app/app:delegated: this command mounts the current folder (which contains all our code) to the working directory of the docker container - see the commands in the Dockerfile
  • -p notused:/opt/react_native_app/app/node_modules: if you don’t use this trick, the node_modules folder of your local machine will override the nodes_modules in Docker - that can lead to problems

Go to http://localhost:19006 and you should be able to see the web version of the React Native app.

Docker Compose

Typing that out is a hassle, so let’s use docker compose.

Inside the main folder, create a docker-compose.yml file with the following content:

version: '2.4'

      context: .
        - NODE_ENV=development
      - NODE_ENV=development
    tty: true
      - '19006:19006'
      - '19001:19001'
      - '19002:19002'
      - ./react_native_app:/opt/react_native_app/app:delegated
      - ./react_native_app/package.json:/opt/react_native_app/package.json
      - ./react_native_app/package-lock.json:/opt/react_native_app/package-lock.json
      - notused:/opt/react_native_app/app/node_modules
      disable: true


Now you can use the following commands:

  • docker-compose build: builds the container
  • docker-compose up: spins up the container
  • docker-compose up -d: spins up the container in detached mode (background)
  • docker-compose down --remove-orphans: brings down the container and cleans up abandoned containers

Common Errors


It’s easy to get confused about the file structure inside the Docker container and how it translate to our local machine.

Make sure that there are no typos. Mount and copy the data from your local machine to the correct location inside the Docker container.

In my example, I use placeholder names like react_native_app or docker-native-app.
I’m sure you will want to customize those to fit your project. In that case, it’s easy to forget to change the names in the Dockerfile or docker-compose.yaml.

Expo CLI

The Expo CLI is quite fragile. Often, dependencies are missing or the container can’t find the correct dependencies.

Missing Dependencies

If you get Build error: missing babel-preset-expo, go inside the folder for your Expo app. In the example it’s react_native_app. Does package.json contain the dependency? If not, add it and rebuild the container.

Inside the react_native-app folder:

npm add babel-preset-expo

Inside the parent folder (docker-react-native):

docker-compose build
docker-compose up -d

.expo Folder

The hidden folder .expo also often causes problems. Delete it and it will rebuild again.

Inside the main folder:

rm -rf react_native_app/.expo
docker-compose up -d

Further Reading