Developing Ionic Apps Docker-Style

In the world of advanced app development there are clear benefits to working with the latest and greatest tools. New or revamped frameworks such as Ionic 2 help us solve common yet complex problems like targeting multiple mobile systems.

Being on the bleeding edge of technology though has its costs. For starters, many valuable features are found in beta releases which increases instability. Dependencies are often mismatched forcing us to try alternative versions until we end up with a compatible set. Available documentation and forums are not always up to speed with these issues leaving trial-and-error as our only choice.

The purpose of this article is to guide us through the setup of a suitable and flexible testing/development sandbox for mobile web apps. We want to do this in a way that allows us to experiment with packages and configurations in a controlled and predictable manner. We also want to be able to start coding once we are happy with the state of the environment, but leaving an option for trying out future releases of third party components.

Ionic 2 is an excellent toolkit that enables us to leverage previous web development expertise and apply that to the building of apps which are platform-specific. It works together with Angular 2 and Cordova while adding UI components, prototyping, automation, cloud services and native API integration to the mix.

To isolate and encapsulate our work we will use Docker, the popular container-based virtualization solution for both deployment and development. Chances are that a number of community-assembled images already exist featuring a compatible set of libraries that might suit our project. But remember we are dealing with pre-release code which is subject to change and so we need a way to rebuild our environment using newly released code.

By creating our own image we can follow the general procedure of trying out the latest version of each compoenent and if necessary, downgrade to a revision compatible with the rest. We can build up on top of one of the official NodeJS images like in the following Dockerfile example:

FROM node:5.12.0
...

You can place this file in a folder called dockerized-ionic-app (or whatever your choice would be, just be consistent throughout the rest of the files). Next we will add instructions to accommodate both fresh and ongoing Ionic 2 projects:

...
COPY package.json /opt/dockerized-ionic-app/
WORKDIR /opt/dockerized-ionic-app
RUN npm install -g cordova && cordova telemetry off && npm install -g ionic@beta
...

Notice that the global installations of Cordova and Ionic are part of the same RUN command to take advantage of Docker’s layered cache. It’s important that they are executed in that particular order and that we turn telemetry off to avoid interactivity as this process will run automated.

Finally, a local npm install is added which will work once the npm configuration file has been generated by the framework:

...
RUN npm install && npm cache clean

In the meantime we should provide an empty or dummy package.json to start up the container successfully without error messages. As usual, we will rebuild the image each time the list of dependencies changes. Docker will optimize that process using its cache.

As with other npm-based projects, we keep our source code in a shared volume that can be modified from the host. Dependent packages however will exist only within the container. Docker Compose can help us achieve this requirement:

app:
build: .
ports:
'8100:8100'
'35729:35729'
volumes:
.:/opt/dockerized-ionic-app
/opt/dockerized-ionic-app/node_modules
...

In this configuration we define app as our only service and set up volumes and ports. We forward the corresponding TCP ports for the http server and live reload to our host. At last we specify the command that will serve our app:

...
command: ionic serve --all

Although we are going to avoid executing this until generating the boilerplate.

We want to be able to start off fresh projects using just the tools inside the container. After building the image for the first time with docker-compose build we can launch a bash console using docker compose and begin issuing the commands for the initial setup. docker-compose run –rm app bash We want all side effects to be captured on the shared volume, to emphasize this we are telling compose to destroy the container on exit by means of the –rm flag.

Neither Ionic nor Cordova accept an existing folder as a base for new apps so a workaround is needed to use our shared volume as base. To do this we will instruct the corresponding start command to use a subfolder that will share the same name as its parent folder. We will start with Cordova:

cordova create dockerized-ionic-app

Setting up Cordova separately allows us to tweak the initial boilerplate, hand picking our platforms and plugins:

cd dockerized-ionic-app
cordova plugin add cordova-plugin-device --save  && cordova plugin add cordova-plugin-console --save  && cordova plugin add cordova-plugin-whitelist --save && cordova plugin add cordova-plugin-splashscreen --save && cordova plugin add cordova-plugin-statusbar --save && cordova plugin add ionic-plugin-keyboard --save
cordova platform add browser --save

In this case we are choosing the browser as the initial target as opposed to iOS, Ionic’s default platform. This will allow us to validate the setup and start development quicker. Once this configuration is complete we can merge it with the rest of the tree:

cd ..
mv dockerized-ionic-app/ .
rmdir dockerized-ionic-app/

Now we can generate the Ionic boilerplate re-using the folder name: ionic start dockerized-ionic-app –skip-npm –v2 –ts –no-cordova This last command should set us up for using Ionic with Angular 2 and Typescript as scripting language. We are also telling the framework not to install any packages until we are able to move the generated source files to their final destination, so let’s move the content over one more time:

cd ..
mv dockerized-ionic-app/ . && mv dockerized-ionic-app/.[!.] .
rmdir dockerized-ionic-app/

At last all the configurations have been merged together in one place. At this point we could go ahead and run the project but if we want these last changes baked into the image so we should rebuild it. This step is only needed of course for newly created projects. Exit the container and use docker-compose to rebuild:

docker-compose build

And then start the container:

docker-compose up

Now we are serving the app and watching source files for development. Head to localhost:8100 to see the app running. Live reload should be in place also so go ahead and try making a few changes, your browser should refresh automatically.

At the moment of writing, this was the final recipe after trying different commands in different order. Notice for instance that we are not using nodejs version 6 because of incompatibilities with one of the dependencies. The point being: Docker allows us to solve issues as we go, starting over when needed and recording the instructions to recreate our environment at any point.

In a second part to this article we will extend this approach and show you how we can add platforms to test our code directly on the device. Stay tuned!