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: postgres
      DB_PASS: postgres
      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: postgres
      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 the helm create example-app-chart command.
  2. Write a deployment object (Kubernetes manifest) for each of the services from the 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