Introduction
In recent years, companies have moved away from monolithic applications to microservices-based applications. Microservices-based applications give you advantages like scalability, easier application maintenance and upgrades, faster time to production, optimal cost, etc. Although, it also poses several issues, mainly in terms of operations to manage the different services.
Containerizing these microservices solved the operational issues to an extent, but still, the large number of microservices prompted the need for container orchestration solutions such as Docker Compose, Kubernetes, HashiCorp Nomad, etc.
Docker Compose enables you to define and run multi-container applications easily. You can define containers and their configurations in a single YAML file called a compose file. You can then start and stop all these services with a single command. Despite these features, Docker Compose also has certain drawbacks, making it less than ideal to use in various environments from Development to Production.
In this article, we will discuss Docker Compose and how you can convert your Docker Compose files to Acornfiles.
Why use Acorn instead of Docker Compose?
Docker compose is an orchestration tool for containerized applications. With this, you can define and run multi-container applications easily. It enables you to use YAML to configure your application services and run them all with a single command.
Kubernetes is also a container orchestrator like Docker Compose and they have distinct pros and cons against each other. Below are some of the pros and cons of Docker Compose against Kubernetes.
Pros:
- Concise file structure for multiple containers
- Single command to start/stop services
- Use local code to build and run containers
Cons:
- Runs on a single node
- Prone to a single point of failure
- Lack of extensibility
Acorn is an application packaging and deployment framework that simplifies running apps on Kubernetes. Learn more about Acorn at docs.acorn.io.
Acorn offers the best of both worlds by implementing the advantages of Docker Compose and using Kubernetes as its backend. With Acorn, you can define all your microservices containers in a single file called the Acornfile and use the Acorn CLI to start/stop all the services together on a pre-configured Kubernetes cluster.
Converting Docker Compose file to Acornfile
Now that we understand the benefits of Acorn, the next order of business is to convert the Docker Compose files to Acornfiles. Here we will see some examples of Docker Compose files and learn how to convert them.
Before we begin converting Docker Compose files to Acornfiles, let’s first understand what an Acornfile is. An Acornfile lets you describe how to build, develop and run containerized applications, quickly and easily. Its syntax is similar to JSON and YAML which you’re most likely familiar with. Go to Acorn documentation for detailed information about Acornfiles.
Prerequisites
The prerequisites here ensure that, after converting to Acornfile, you’re able to run your application and confirm that it is working as expected. For that, make sure you have
- A running Kubernetes cluster.
- Your kube config is set with the right context.
- Install Acorn on your cluster using install instructions.
Converting Flask-Redis Docker Compose file to Acornfile
Note: Please refer to my fork of the awesome-compose repository to see the fully converted Acornfiles of the examples used here.
Let’s start with a simple Docker Compose file and convert it to Acornfile. For that purpose, we’ll first consider the flask-redis example from the awesome-compose repository. This repository has a bunch of sample compose files for different application stacks. Below is what the compose file for the chosen application looks like.
services:
redis:
image: redislabs/redismod
ports:
- '6379:6379'
web:
build:
context: .
target: builder
# flask requires SIGINT to stop gracefully
# (default stop signal from Compose is SIGTERM)
stop_signal: SIGINT
ports:
- '8000:8000'
volumes:
- .:/code
depends_on:
- redis
You can see that it contains two containers, one for Redis and the other for a Python Flask application.
The Flask container, named as a web
container in the spec, first builds the application using a Dockerfile
and other code files in the current directory. It exposes the container port 8000
to the host and mounts the current host directory onto the container for code hot reloading. Lastly, the Flask container declares that it should be started after the Redis container.
An Acornfile
is made up of objects. To convert this Compose file to Acornfile
, we’ll start by defining a containers
object which will contain Redis
and web
containers. In the Redis container, we’ll add the image name, declare the Redis port and publish it.
containers: {
redis: {
image: "redislabs/redismod"
ports: publish: "6379/tcp"
}
web: {
}
}
Next, we’ll add a build section for the web container and specify the context as well as a multi-stage target to build.
containers: {
...
web: {
build: {
context: "."
target: "builder"
}
}
}
We’ll ignore sending the SIGINT
signal to the flask container, although like Docker, Kubernetes also sends a SIGTERM signal to the pod to terminate it. It then waits for 30 seconds before sending a SIGKILL
signal. Kubernetes doesn’t support sending a different container termination signal but has a few other ways to gracefully terminate the pod, such as defining a PreStop
hook. Currently, Acorn doesn’t support defining container hooks. Hence, we’ll let the default Kubernetes termination behavior run for now.
Next, we’ll define the port for the web
container and expose it. We’ll also declare that the container should be started after the Redis container.
containers: {
...
web: {
build: {
context: "."
target: "builder"
}
ports: publish: "8000/http"
dependsOn: ["redis"]
}
}
Since the web container is mounting the host code directory for development purposes, you need to have another version of the Docker Compose file for the production environment. Acorn lets you conditionally mount directories so that you can use the same Acornfile across all environments.
The last line in the Acornfile below will mount the current directory only if the application is running in debug mode. You can learn more about using Acorn debug mode in my previous blog post.
Below we have the complete Acornfile
equivalent to the flask-redis Docker Compose file.
containers: {
redis: {
image: "redislabs/redismod"
ports: publish: "6379/tcp"
}
web: {
build: {
context: "."
target: "builder"
}
ports: publish: "8000/http"
dependsOn: ["redis"]
if args.dev { dirs: "/code": "./" }
}
}
You can now run the Acorn application and make sure it works as expected. Refer to my previous blog post for more details on setting up Acorn and running applications with it.

Convert Nginx Flask MySql application Docker Compose file to Acornfile
For the next example we’ll convert the nginx-flask-mysql application Docker Compose file, which is a bit more complex than the previous example. Here’s what the Compose file for the application looks like.
services:
db:
# We use a mariadb image which supports both amd64 & arm64
architecture
image: mariadb:10-focal
# If you really want to use MySQL, uncomment the following
line
#image: mysql:8
command: '--default-authentication-plugin=mysql_native_pass
word'
restart: always
healthcheck:
test: ['CMD-SHELL', 'mysqladmin ping -h 127.0.0.1 --passw
ord="$$(cat /run/secrets/db-password)" --silent']
interval: 3s
retries: 5
start_period: 30s
secrets:
- db-password
volumes:
- db-data:/var/lib/mysql
networks:
- backnet
environment:
- MYSQL_DATABASE=example
- MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db-password
expose:
- 3306
- 33060
backend:
build:
context: backend
target: builder
restart: always
secrets:
- db-password
ports:
- 8000:8000
networks:
- backnet
- frontnet
depends_on:
db:
condition: service_healthy
proxy:
build: proxy
restart: always
ports:
- 80:80
depends_on:
- backend
networks:
- frontnet
volumes:
db-data:
secrets:
db-password:
file: db/password.txt
networks:
backnet:
frontnet:
As you can see, it has three containers, a volume, a secret and network definitions as well as a few more extra configurations in each container than in the previous example.
So to begin with, let’s create an Acornfile
with a containers
object containing three containers for each service definition as above. Let’s also populate the three containers with basic information such as image
, ports
, build instructions and the containers they depend on before starting.
containers: {
db: {
image: "mariadb:10-focal"
// If you really want to use MySQL, uncomment the following line
//image: "mysql:8"
ports: [ 3306, 33060 ]
}
backend: {
build: {
context: "backend"
target: "builder"
}
ports: publish: "8000/http"
dependsOn: [ "db" ]
}
proxy: {
build: context: "proxy"
ports: publish: "80/http"
dependsOn: [ "backend" ]
}
}
Please note that since the DB
container service in the Docker Compose file doesn’t publish its ports, we haven’t published them using Acornfile either.
Next, we’ll add the command for the DB
container. We’ll ignore the restart: always
directive since Acorn runs each container on Kubernetes as a Deployment, which takes care of restarting a failed/stopped pod.
containers: {
db: {
image: "mariadb:10-focal"
// If you really want to use MySQL, uncomment the following line
//image: "mysql:8"
cmd: ["--default-authentication-plugin=mysql_native_password"]
ports: [ 3306, 33060 ]
}
...
The healthcheck
directive in the DB
container is to override the healthcheck instruction in Dockerfile
and add another health
status to the container. The healthcheck instruction in Docker keeps checking the health of the container repeatedly at a set interval. Hence, we’ll convert it to a liveness probe
in Acornfile as below. You can read more about defining various probes in Acorn, in the Acorn documentation. You may also find more details on the healthcheck
instruction in the Docker Compose and Docker documentation.
containers: {
db: {
image: "mariadb:10-focal"
// If you really want to use MySQL, uncomment the following line
//image: "mysql:8"
cmd: ["--default-authentication-plugin=mysql_native_password"]
probes: {
"liveness": {
exec: {
command: [ "/bin/sh", "-c", "mysqladmin ping -h 127.0.0.1 --password=$MYSQL_ROOT_PASSWORD --silent" ]
}
periodSeconds: 3
failureThreshold: 5
initialDelaySeconds: 30
}
}
ports: [ 3306, 33060 ]
}
...
We’ll nowl define and configure the secret for the DB password. Since Docker Compose mounts the secrets onto containers as files at /run/secrets/<secret_name>
, the backend
Flask app is wired to read the DB password from a file only. I’ve made an effort here to convert the Docker Compose file, without needing to change anything in the application.
containers: {
db: {
image: "mariadb:10-focal"
// If you really want to use MySQL, uncomment the following line
//image: "mysql:8"
cmd: ["--default-authentication-plugin=mysql_native_password"]
probes: {
"liveness": {
exec: {
command: [ "/bin/sh", "-c", "mysqladmin ping -h 127.0.0.1 --password=$MYSQL_ROOT_PASSWORD --silent" ]
}
periodSeconds: 3
failureThreshold: 5
initialDelaySeconds: 30
}
}
env: {
"MYSQL_DATABASE": "example"
"MYSQL_ROOT_PASSWORD": "secret://db-password/token"
}
ports: [ 3306, 33060 ]
}
backend: {
build: {
context: "backend"
target: "builder"
}
files: {
"/run/secrets/db-password": "secret://db-password/token?mode=0400"
}
ports: publish: "8000/http"
dependsOn: [ "db" ]
}
...
secrets: {
"db-password": type: "token"
}
In the secrets object, we’ve defined a secret named db-password
of type token. It is used to generate a new password every time, as opposed to the static password used by the Docker Compose application. We load the secret value onto the DB container as an environment variable. We also load the same secret on the backend container but as a file for the previously mentioned reason.
Lastly, we’ll declare a new empty directory volume to be used as a data directory for the DB. We’ll also attach it to the DB container.
Thus, the full Acornfile would look as below.
containers: {
db: {
image: "mariadb:10-focal"
// If you really want to use MySQL, uncomment the following line
//image: "mysql:8"
cmd: ["--default-authentication-plugin=mysql_native_password"]
probes: {
"liveness": {
exec: {
command: [ "/bin/sh", "-c", "mysqladmin ping -h 127.0.0.1 --password=$MYSQL_ROOT_PASSWORD --silent" ]
}
periodSeconds: 3
failureThreshold: 5
initialDelaySeconds: 30
}
}
env: {
"MYSQL_DATABASE": "example"
"MYSQL_ROOT_PASSWORD": "secret://db-password/token"
}
dirs: "/var/lib/mysql": "volume://db-data"
ports: [ 3306, 33060 ]
}
backend: {
build: {
context: "backend"
target: "builder"
}
files: {
"/run/secrets/db-password": "secret://db-password/token?mode=0400"
}
ports: publish: "8000/http"
dependsOn: [ "db" ]
}
proxy: {
build: context: "proxy"
ports: publish: "80/http"
dependsOn: [ "backend" ]
}
}
volumes: {
"db-data": {}
}
secrets: {
"db-password": type: "token"
}
You might have noticed that we haven’t done anything for the Networks defined in the Docker Compose file. That’s because the custom networks defined here are used to separate services from each other and restrict traffic to and from different containers. In this particular case, the proxy and DB services can talk to the backend service, but not to each other. We can define such network topologies in Kubernetes using Network Policies.
As of now, Acorn does not have a way of defining Network Policies and we can’t convert this part to Acorn, but there’s a feature request for it. Hence ,let’s define the network policies ourself and apply it.
$ APP_NAMESPACE=$(acorn app nginx-flask-mysql -ojson | jq -r .status.namespace)
$ kubectl create -f https://raw.githubusercontent.com/samkulkarni20/awesome-compose/main/nginx-flask-mysql/network-policy.yaml -n $APP_NAMESPACE
Conclusion
We just saw how we can convert your existing Docker Compose files into Acornfiles and run your existing applications on a Kubernetes cluster without the need to learn Kubernetes. If you want to learn more about Acornfiles, please refer to Acorn documentation. You can also check out Acornfile reference for detailed information on every element of the Acornfile. Please refer to my previous blog post to learn how you can easily develop containerized applications for Kubernetes using Acorn and how to set up Acorn on your Kubernetes cluster. You can also Acorn training class to learn more about Acorn and all of its benefits.
Sameer Kulkarni is a software engineer with 14+ years of experience and is working as a Principal Engineer at InfraCloud. You can chat with him on Twitter and read more of his work on Medium.