Rapid code iteration with Acorn development mode

Jan 23, 2023 by Luc Juggery
Rapid code iteration with Acorn development mode

This is the fourth part in a series focused on teaching the fundamentals of building and developing applications using Acorn. In the previous post of this series we enhanced the Acornfile of the VotingApp defining volumes for both db and redis containers. In this post we will use Acorn’s development mode to accelerate and ease the development workflow.

About development mode

In development mode , Acorn allows you to make changes to the source code and see it updated inside the app containers in real time. In this mode Acorn will watch the local directory for changes and synchronize them to the running Acorn app. To activate the development mode we need to use the -i flag when running the app.

In this post we will focus on the result microservice which is developed with Node.js. If you have a look into the folder containing the application code, you will see 2 build targets are defined in the Dockerfile: the first one is named dev, the second one production:

WORKDIR /app COPY . . EXPOSE 5000 FROM base as dev ENV NODE_ENV=development RUN npm ci CMD ["npm", "run", "dev"] FROM base as production ENV NODE_ENV=production RUN npm ci --production CMD ["npm", "start"]

– when the image is built for the dev target, the command npm run dev runs nodemon under the hood. nodemon is a process which watches the code changes and which is able to relaunch the application

– when the image is built for the production target, the command npm start runs the standard node binary. No hot reload is possible in that case

Before running the Acorn application in development mode we need to modify the Acornfile is bit to make sure that if the dev mode is detected:

  • the code folder of the result microservice is mounted into the /app folder within the container
  • the build is done against the dev target, this will ensure nodemon is the main process running in the result container thus making hot reload possible Modify the definition of the result container so it looks as follows:
build: { target: std.ifelse(args.dev, "dev", "production") context: "./result" } if args.dev { dirs: { "/app": "./result" } } ports: "5000/http" env: { "POSTGRES_USER": "secret://db-creds/username" "POSTGRES_PASSWORD": "secret://db-creds/password" } }

Note: Acorn provides many useful functions such as the std.ifelse , an helper to perform an if…else…end statement on a single line. All the function available are listed in the function library documentation.

You can now update the application running it in development mode:

undefined

note: in development mode you’ll notice that the logs of each containers are streamed to the console

Once the application is up and running you can modify the server.js file located in the result folder and see that changes automatically been taken into account making the container’s process to restart.

Below is an example of logs you can get for the result service following a change in server.js:

carbon-980x321.png

We illustrated above the development mode for the result microservice but the same principles would apply for the other microservices as well. We can change the Acornfile modifying the definition of the voteui, vote and resultui containers to ensure the development mode is working fine for those ones as well.

if args.dev { dirs: { "/usr/share/nginx/html": "./vote-ui" } } build: { context: "./vote-ui" } ports: publish : "80/http" } vote: { build: { target: std.ifelse(args.dev, "dev", "production") context: "./vote" } if args.dev { dirs: { "/app": "./vote" } } ports: "5000/http" } resultui: { build: { target: std.ifelse(args.dev, "dev", "production") context: "./result-ui" } if args.dev { dirs: { "/app": "./result-ui" } } ports: publish : "80/http" }

Once you have modified the Acornfile you can update the application one more time:

undefined

We can go one step further and make sure volumes are not used when the app is run in development mode.

In order to do that we need to:

  • add a condition in the volumes key so that no volumes are created in dev mode:
if !args.dev { "db": { size: "100M" } "redis": { size: "100M" } } }
  • add a condition in redis container so content of /data is not persisted in a volume while in dev mode
labels: { component: "redis" } image: "redis:7.0.5-alpine3.16" ports: "6379/tcp" dirs: { if !args.dev { "/data": "volume://redis" } } }
  • add a condition in db container so content of_ /var/lib/postgresql/data_ is not persisted in a volume while in dev mode
labels: { component: "db" } image: "postgres:15.0-alpine3.16" ports: "5432/tcp" env: { "POSTGRES_USER": "secret://db-creds/username" "POSTGRES_PASSWORD": "secret://db-creds/password" } dirs: { if !args.dev { "/var/lib/postgresql/data": "volume://db" } } }

Wrapping up

In this example we explained how to use Acorn development mode to speed up the whole development cycle of the application as changes done locally are immediately taken into account by the running containers.

In the next post of the series we’ll discuss about args and profile which are often used to provide a dynamic configuration to an application. If you’d like to learn more about working with Acorn, you can register for an upcoming live online training.

Luc Juggery is a software engineer with 18+ years of experience and co-founder of 2 startups located in Sophia-Antipolis, southern France. You can chat with him on Twitter, read more of his work on Medium, find his tutorials on YouTube, or take one of his Docker or Kubernetes training courses on Udemy.