Follow this guide to package your application as a Helm chart using the docker-compose.yaml file that you already have. We will use a simple example containing an application connected to a PostgreSQL database.

compose.yml
volumes:
  pg_app_data:

services:
  example-app:
    image: public.aws.ecr/<repository_id>/example-app:1.0.0
    environment:
      DB_HOST: database
      DB_NAME: app_db
      DB_USER: postrgres
      DB_PASS: postrgres
      DB_PORT: 5432
      APP_PORT: 8000
    ports:
      - '8000:8000'
    depends_on:
      - database

  database:
    image: postgres:14.8-alpine3.18
    environment:
      POSTGRES_DB: app_db
      POSTGRES_USER: postrgres
      POSTGRES_PASSWORD: postgres
    volumes:
      - pg_app_data:/var/lib/postgresql/data
    ports:
      - '5432:5432'

Create a helm chart from your Docker Compose

  1. Create a directory and set up the Helm Chart Directory Structure in it using helm create example-app-chart command.
  2. Write a deployment object (kubernetes manifest) for each of the services from docker-compose.yaml file, in the templates directory.They define the desired state for application pods, ensuring that the specified number of replicas of the application is running at any given time.
templates/deployments.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres-statefulset
spec:
  serviceName: '{{ .Values.db.host }}'
  replicas: 1
  selector:
    matchLabels:
      app: postgres-db
  template:
    metadata:
      labels:
        app: postgres-db
    spec:
      containers:
        - name: postgres-container
          image: postgres:14.8-alpine3.18
          env:
            - name: POSTGRES_DB
              value: '{{ .Values.db.name }}'
            - name: POSTGRES_USER
              value: '{{ .Values.db.user }}'
            - name: POSTGRES_PASSWORD
              value: '{{ .Values.db.pass }}'
            - name: PGDATA
              value: /var/lib/postgresql/data/todo/
          ports:
            - containerPort: { { .Values.db.port } }
          volumeMounts:
            - name: postgres-storage
              mountPath: /var/lib/postgresql/data

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-app-deployment
spec:
  replicas: { { .Values.replicaCount } }
  selector:
    matchLabels:
      app: example-app
  template:
    metadata:
      labels:
        app: example-app
    spec:
      containers:
        - name: example-app-container
          image: '{{ .Values.example-app.image }}:{{ .Values.example-app.imageVersion }}'
          env:
            - name: DB_HOST
              value: '{{ .Values.db.host }}'
            - name: DB_PORT
              value: '{{ .Values.db.port }}'
            - name: DB_NAME
              value: '{{ .Values.db.name }}'
            - name: DB_USER
              value: '{{ .Values.db.user }}'
            - name: DB_PASS
              value: '{{ .Values.db.pass }}'
            - name: HELM_VERSION
              value: '{{ .Chart.Version }}'
            - name: APP_PORT
              value: '{{ .Values.example-app.service.port }}'

          ports:
            containerPort: { { .Values.example-app.service.port } }
      initContainers:
        - name: check-db-ready
          image: postgres:14.8-alpine3.18
          env:
            - name: POSTGRES_HOST
              value: '{{ .Values.db.host }}'
            - name: POSTGRES_PORT
              value: '{{ .Values.db.port }}'
          command:
            [
              'sh',
              '-c',
              'echo Checking if postgres is up; until pg_isready -h $POSTGRES_HOST -p $POSTGRES_PORT; do echo Waiting
              for postgres database to be up...; sleep 2; done; echo Postgres is up!',
            ]

We can see that example-app service depends on the database service in the docker-compose file. The depends_on field from Docker Compose, which defines dependencies between services, can be mimicked using initContainers in a Deployment object. initContainers run to completion before the main application containers start. They can be used to ensure that a dependent service is ready before starting the main application.

  1. Write a service object (kubernetes manifest) for each of the services from docker-compose.yaml file, in the templates directory, to expose the ports your services run on accordingly.
templates/services.yaml
apiVersion: v1
kind: Service
metadata:
  name: example-app-service
spec:
  type: { { .Values.example-app.service.type } }
  selector:
    app: example-app
  ports:
    - protocol: TCP
      port: { { .Values.example-app.service.port } }
      targetPort: { { .Values.example-app.service.targetPort } }

---
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
spec:
  selector:
    app: postgres-db
  ports:
    - protocol: TCP
      port: 5432

The service.type field specifies how a service is exposed. Learn more about service types and their values

  1. Create values.yaml file with appropriate values required by your helm chart
values.yaml
replicaCount: 1

example-app:
  service:
    type: NodePort
    port: 8000
    targetPort: 8000
  image: public.ecr.aws/<repository_id>/example-app
  imageVersion: 1.0.0

db:
  host: postgres-service
  port: 5432
  name: app_db
  user: postgres
  pass: postgres
  1. Now that you’ve created a helm chart using your Docker Compose follow this guide to publish your helm chart and get it deployed in a Localops application environment.

Next Steps