In this series of articles we will use a sample microservice application and show how we can build / run / package and distribute it using Acorn.
The current article (first in the series) will introduce our demo application and show how it can be defined and run using a simple Acornfile. As a demo environment we will use a 2-node cluster created on Civo, a cloud provider offering k3s managed clusters. If you want to follow along this tutorial you can also create a simple local cluster install an ingress controller and acorn on it.
About the sample application
To demo several features of Acorn we will use the Voting App, a demo microservices application originally created by Docker. This application might not follow all the best architectural patterns but it’s definitely great for demos as it uses several languages, different components, and it’s very easy to understand. It basically allows a user to vote from a web interface and to see the results from another one.


The application architecture is as follows:

Below are the details of each microservice:
- vote-ui is a web UI built with VueJS, it allows a user to choose an item between Cats and Dogs
- vote is a backend exposing an API built with Python / Flask
- redis is the database where votes are stored
- worker is a service that retrieves votes from redis and stores the results in a postgres database. Several declinations exist for that microservice: Java, .NET, Go, Rust
- db is the database (based on Postgres) in which the worker consolidates the votes
- result is the backend sending votes to the result-ui via websocket
- result-ui is a web UI built with Angular, it displays the results of the votes
This app was originally living in a single GitHub repo. In order to enhance it I created a GitLab project inside of which each microservice has its own git repository.
To prepare the following steps, we create a new folder and clone the application code inside of it:
mkdir votingapp
cd votingapp
for s in vote-ui vote worker result result-ui; do
git clone https://gitlab.com/voting-application/$s.git;
done
In the next step we will see how this application can be defined with Acorn.
Defining the application in an Acornfile
As described in the documentation, an Acornfile contains the following top level elements:
- args: defines arguments the consumer can provide
- profiles: defines a set of default arguments for different deployment types
- containers: defines the containers to run the application
- volumes: defines persistent storage volumes for the containers to consume
- jobs: defines tasks to run on changes or via cron
- acorns: other Acorn applications that need to be deployed with your app
- secrets: defines secret bits of data that are automatically generated or passed by the user
- localData: default data and configuration variables
To represent the microservices of the VotingApp, we create an Acornfile in the votingapp folder. This file only contains the containers top level key:
containers: {
}
Next, we add inside of it an element for each microservice:
containers: {
voteui: {
}
vote: {
}
redis: {
}
worker: {
}
db: {
}
result: {
}
resultui: {
}
}
As the microservice will run in containers, we need to specify how the containers can be built or which image it is based on:
- for the vote-ui, vote, worker, result and result-ui microservices we use the build.context property to reference the location of the Dockerfile that will be used to build the image
- for db and redis we specify the image property
Our Acornfile now looks as follows:
containers: {
voteui: {
build: {
context: "./vote-ui"
}
}
vote: {
build: {
context: "./vote"
}
}
redis: {
image: "redis:6.2-alpine3.13"
}
worker: {
build: {
context: "./worker/go"
}
}
db: {
image: "postgres:13.2-alpine"
}
result: {
build: {
context: "./result"
}
}
resultui: {
build: {
context: "./result-ui"
}
}
}
For the postgres image to run we need to provide it the POSTGRES_PASSWORD environment variable. In this example we also define the POSTGRES_USER.
The definition of the db container is as follows:
db: {
image: "postgres:13.2-alpine"
env: {
"POSTGRES_USER": "postgres"
"POSTGRES_PASSWORD": "postgres"
}
}
As result needs to connect to db, we specify the credentials in that container too:
result: {
build: "./result"
ports: "5000/http"
env: {
"POSTGRES_USER": "postgres"
"POSTGRES_PASSWORD": "postgres"
}
}
worker also communicates with db so we also give it the credentials it needs:
worker: {
build: "./worker/go"
env: {
"POSTGRES_USER": "postgres"
"POSTGRES_PASSWORD": "postgres"
}
}
I do agree providing secrets in plain text is definitely not the way to go. In a future article in the series we’ll show how to use Acorn secrets to have a much cleaner / safer approach.
In order for the container of the application to communicate with each other we need to define the network ports for each one. As defined in the documentation, there are 3 scopes to specify the ports:
- internal allows communication between containers within the same Acorn app
- expose allows communication between containers within the cluster
- publish allows containers to be reached from outside of the cluster
As vote, result, redis and db microservices only need to be reachable from other containers within the same application, we use the default internal scope for each of them.
As vote-ui and result-ui need to be reachable from the outside world we use the publish scope for both of them.
The Acornfile now looks as follows:
containers: {
voteui: {
build: "./vote-ui"
ports: publish : "80/http"
}
vote: {
build: "./vote"
ports: "5000/tcp"
}
redis: {
image: "redis:6.2-alpine3.13"
ports: "6379/tcp"
}
worker: {
build: "./worker/go"
env: {
"POSTGRES_USER": "postgres"
"POSTGRES_PASSWORD": "postgres"
}
}
db: {
image: "postgres:13.2-alpine"
ports: "5432/tcp"
env: {
"POSTGRES_USER": "postgres"
"POSTGRES_PASSWORD": "postgres"
}
}
result: {
build: "./result"
ports: "5000/http"
env: {
"POSTGRES_USER": "postgres"
"POSTGRES_PASSWORD": "postgres"
}
}
resultui: {
build: "./result-ui"
ports: publish : "80/http"
}
}
We now have a first (minimal) version of the Acornfile which specifies the application. Let’s make sure the Voting App can be run using that one.
Testing the application
We run the Voting Application using the acorn CLI:
acorn run -n vote .
It takes a few tens of seconds for the application to be up and running. When it’s ready we are provided http endpoints on the acorn.io domain for both vote-ui and result-ui containers:

- result-ui: http://voteui-vote-5ccf7969.po4pog.alpha.on-acorn.io

Using the acorn CLI we can visualize all the acorn resources created:
% acorn all
APPS:
NAME IMAGE HEALTHY UP-TO-DATE CREATED ENDPOINTS MESSAGE
vote e48599ee3ff2 7 7 20m ago http://resultui-vote-5ccf7969.po4pog.alpha.on-acorn.io => resultui:80, http://voteui-vote-5ccf7969.po4pog.alpha.on-acorn.io => voteui:80 OK
CONTAINERS:
NAME APP IMAGE STATE RESTARTCOUNT CREATED MESSAGE
vote.db-6dcdc586fc-9v6m2 vote postgres:15.0-alpine3.16 running 0 20m ago
vote.redis-76f88c775-67l9j vote redis:7.0.5-alpine3.16 running 0 20m ago
vote.result-5c467bbf6c-hz7s9 vote sha256:ca344fce985f8218a8dbdcda436dadb6ac42abb596c906be5504176bef2a3903 running 0 20m ago
vote.resultui-54f8b6c865-bfw7h vote sha256:bd93fe432c1322b331c5b23057513bbdc01be5e3373eee617a31f4a507feeb7c running 0 20m ago
vote.vote-db445dfcc-bhxzd vote sha256:8d027e0823d9fb091d9e27ba8b8e01d0b7bfffc47158e08ea8a7d056ca537be3 running 0 20m ago
vote.voteui-6cf4bb8b7d-5t4jr vote sha256:872361865af0f935cf45267e0b615bf7c9acfec87391bdac172690a1dde81bae running 0 20m ago
vote.worker-6446d5cfff-x8c4v vote sha256:e3722b63712a85259ee3868620d45fb2266ed13d44149e06bf5015bbc35c027e running 0 20m ago
VOLUMES:
NAME APP-NAME BOUND-VOLUME CAPACITY STATUS ACCESS-MODES CREATED
SECRETS:
ALIAS NAME TYPE KEYS CREATED
The application’s containers have been created and exposed. Currently there are no secrets nor volumes as we did not defined those top level elements in the Acornfile.
If you are curious about what happened under the hood, we could see that a new Kubernetes namespace has been created in the cluster, this one is dedicated to our newly created acorn application:
$ kubectl get ns
NAME STATUS AGE
default Active 4h48m
kube-system Active 4h48m
kube-public Active 4h48m
kube-node-lease Active 4h48m
traefik Active 4h8m
acorn Active 3h56m
acorn-system Active 3h56m
vote-5ccf7969-b2f Active 34m <strong><- namespace created for the application</strong>
Within this namespace there are a Deployment / Pod and a Service for each microservice of the Voting App:
$ kubectl get all -n vote-5ccf7969-b2f
NAME READY STATUS RESTARTS AGE
pod/worker-6446d5cfff-x8c4v 1/1 Running 0 27m
pod/voteui-6cf4bb8b7d-5t4jr 1/1 Running 0 27m
pod/redis-76f88c775-67l9j 1/1 Running 0 27m
pod/vote-db445dfcc-bhxzd 1/1 Running 0 27m
pod/result-5c467bbf6c-hz7s9 1/1 Running 0 27m
pod/resultui-54f8b6c865-bfw7h 1/1 Running 0 27m
pod/db-6dcdc586fc-9v6m2 1/1 Running 0 27m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/db ClusterIP 10.43.150.236 <none> 5432/TCP 27m
service/redis ClusterIP 10.43.242.30 <none> 6379/TCP 27m
service/result ClusterIP 10.43.224.90 <none> 5000/TCP 27m
service/resultui ClusterIP 10.43.72.204 <none> 80/TCP 27m
service/vote ClusterIP 10.43.239.217 <none> 5000/TCP 27m
service/voteui ClusterIP 10.43.81.237 <none> 80/TCP 27m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/worker 1/1 1 1 27m
deployment.apps/voteui 1/1 1 1 27m
deployment.apps/redis 1/1 1 1 27m
deployment.apps/vote 1/1 1 1 27m
deployment.apps/result 1/1 1 1 27m
deployment.apps/resultui 1/1 1 1 27m
deployment.apps/db 1/1 1 1 27m
NAME DESIRED CURRENT READY AGE
replicaset.apps/worker-6446d5cfff 1 1 1 27m
replicaset.apps/voteui-6cf4bb8b7d 1 1 1 27m
replicaset.apps/redis-76f88c775 1 1 1 27m
replicaset.apps/vote-db445dfcc 1 1 1 27m
replicaset.apps/result-5c467bbf6c 1 1 1 27m
replicaset.apps/resultui-54f8b6c865 1 1 1 27m
replicaset.apps/db-6dcdc586fc 1 1 1 27m
On top of that, an Ingress resource has been created so the web interfaces (vote-ui and result-ui) can be exposed through the cluster’s Ingress Controller (Traefik in our setup):
$ kubectl get ingress -n vote-5ccf7969-b2f
NAME CLASS HOSTS ADDRESS PORTS AGE
voteui <none> voteui-vote-5ccf7969.po4pog.alpha.on-acorn.io 74.220.24.195 80 31m
resultui <none> resultui-vote-5ccf7969.po4pog.alpha.on-acorn.io 74.220.24.195 80 31m
We can then remove the application using the following command:
acorn rm vote
Wrapping up
In this article we started to specify the Voting App in an Acornfile, we only used the containers top level key for that. In the next article we will build on this example and use the acorn secrets to make the whole application more secure.
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.