DEV Community

Cover image for Day 31: Deploying a Multi-Stage Application with Kubernetes and Helm: My Beginner's Journey
Arbythecoder
Arbythecoder

Posted on

1

Day 31: Deploying a Multi-Stage Application with Kubernetes and Helm: My Beginner's Journey

This post details my experience deploying a simple Node.js "Hello World" application across development, staging, and production environments using Kubernetes, Helm, and GitHub Actions. While the application itself is basic, the process highlights key concepts applicable to more complex projects. I'll share the challenges I faced, the decisions I made, and suggestions for future improvements.

Prerequisites:

Before starting, I ensured I had:

  • A Kubernetes cluster (I used Minikube for local development; for production, a cloud-based solution would be preferable).
  • kubectl installed and configured correctly. This took a bit of troubleshooting initially, ensuring the right context was set.
  • Helm installed and functioning.
  • A GitHub account.

Step 1: The Application – A Simple Node.js Server

I created a minimal Node.js server (server.js):

const express = require('express');
const app = express();
const port = process.env.PORT || 3000;

app.get('/', (req, res) => {
  res.send('Hello from ' + process.env.NODE_ENV + '!');
});

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});
Enter fullscreen mode Exit fullscreen mode

And its corresponding Dockerfile:

FROM node:18

WORKDIR /app

COPY package*.json ./

RUN npm install

COPY . .

EXPOSE 3000

CMD [ "node", "server.js" ]
Enter fullscreen mode Exit fullscreen mode

Building and pushing the image to Docker Hub was straightforward:

docker build -t <my-docker-hub-username>/multi-stage-app:dev .
docker push <my-docker-hub-username>/multi-stage-app:dev
Enter fullscreen mode Exit fullscreen mode

Step 2: Defining Environments and Namespaces

Creating separate namespaces in Kubernetes was crucial for isolation:

kubectl create namespace dev
kubectl create namespace staging
kubectl create namespace production
Enter fullscreen mode Exit fullscreen mode

Step 3: Creating and Configuring the Helm Chart

I started with helm create multi-stage-app and then modified the templates/deployment.yaml and templates/service.yaml files. The deployment.yaml file was particularly important for setting up environment variables and specifying the image. I made sure to use the {{ .Values.env.NODE_ENV }} to dynamically set the NODE_ENV variable based on the values file used during deployment.

Step 4: Helm Values Configuration

This is where the magic of Helm's flexibility came in. I created separate values files: values.yaml, values-dev.yaml, values-staging.yaml, and values-prod.yaml. The values-staging.yaml and values-prod.yaml files allowed me to easily adjust replica counts and other parameters based on the environment. I chose a LoadBalancer service type for dev for easy local access, and ClusterIP for staging and production to minimize costs.

Step 5: Helm Deployments

Deploying with Helm was surprisingly easy:

helm install dev-release ./multi-stage-app -n dev -f values-dev.yaml
helm install staging-release ./multi-stage-app -n staging -f values-staging.yaml
helm install prod-release ./multi-stage-app -n production -f values-prod.yaml
Enter fullscreen mode Exit fullscreen mode

Step 6: Automating with CI/CD (GitHub Actions)

The GitHub Actions workflow (deploy.yml) was the most challenging part. I initially struggled with the manual approval step for production deployments. The solution was to use conditional logic within the workflow.

# ... (Workflow definition) ...
  deploy:
    # ...
    - name: Deploy to Production (Manual Approval)
      if: github.event_name == 'push' && github.ref == 'refs/heads/main'
      needs: manual-approval
      run: |
        helm upgrade --install prod-release ./multi-stage-app -n production -f values-prod.yaml
  manual-approval:
    # ...
Enter fullscreen mode Exit fullscreen mode

Suggestions for Improvement:

  • More Robust Health Checks: The current deployment lacks robust health checks. Integrating a liveness and readiness probe within the Kubernetes deployment definition would significantly improve reliability.
  • Secrets Management: Hardcoding sensitive information (like database credentials) directly in the values files is insecure. A secrets management solution (like Kubernetes Secrets or HashiCorp Vault) should be implemented.
  • Advanced Ingress Controller: For production, an advanced ingress controller (like Nginx or Traefik) would provide features like SSL termination and load balancing.
  • Automated Rollbacks: The CI/CD pipeline should include automated rollbacks in case of deployment failures.
  • Monitoring and Logging: Integrating monitoring and logging tools (like Prometheus and Grafana) is crucial for observability and troubleshooting.

Remember to replace <my-docker-hub-username> with your actual Docker Hub username.

Billboard image

Monitor more than uptime.

With Checkly, you can use Playwright tests and Javascript to monitor end-to-end scenarios in your NextJS, Astro, Remix, or other application.

Get started now!

Top comments (0)

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay