# How to Implement End-to-End DevOps on a Go Lang Demo News Application

In the fast-paced world of software development, adopting DevOps practices is essential for efficient workflows, automated pipelines, and quick delivery. If your project isn't "DevOpsified" yet, you might be missing out on the benefits of continuous integration, containerization, and a smoother deployment process. Today, we're walking through a hands-on approach to transforming a basic Go lang demo news application ("**Demo News App**") into a fully DevOps-enabled project. l will guide you through the complete process, including setting up CI/CD, containerizing the application, creating Kubernetes manifests, and more. Let’s dive right in

## Step 1 : Containerization

The first step is to containerize the application using Docker. We will write a multi-stage Dockerfile to ensure that our images are both secure and lightweight. The Dockerfile will include:

A base image for building the application.

A distroless image for running the application.

This approach not only reduces the image size but also enhances security by minimizing the attack surface.

First you need to understand the application. Here we're dealing with a Go-based portfolio web app. So, we will first run the application locally to understand how it works, what ports it uses, and how it’s built.

First, clone the repository from GitHub using:

```plaintext
git clone https://github.com/sgawali/news-demo-app.git
```

Now refer to readme file and follow all prerequisites & get started steps to run demo app locally

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1729590934383/8f29266f-c650-4f13-87d3-30e714aad52f.png align="center")

As shown in screenshot News Demo App will be launched in your browser. We can type any topic to search news related to that topic in search window and then hit enter to view all news related to that topic.

Writing the Multi-Stage DockerFile:

Using a **multi-stage Dockerfile** saves space and adds security layers to the Docker image. We first use Go lang base image to build the app and then copy it into a minimal distribution image, ensuring we reduce the size and potential vulnerabilities.

Here's a snapshot of what the Dockerfile looks like:

```plaintext
FROM golang:1.23 AS base

WORKDIR /app
# We want to populate the module cache based on the go.{mod,sum} files.
COPY go.mod .
COPY go.sum .

RUN go mod download

COPY . .

# Build the Go app
RUN go build -o ./out/news-demo-app .

# Start fresh from a smaller distroless image
FROM gcr.io/distroless/base

COPY --from=base /app/out/news-demo-app /app/news-demo-app

COPY --from=base /app/assets ./assets

COPY --from=base /app/news ./news

COPY --from=base /app/index.html .

# This container exposes port 3000 to the outside world
EXPOSE 3000

# Run the binary program produced by `go install`
CMD ["/app/news-demo-app"]
```

This Dockerfile is an industry-standard approach. It builds the app, then packages it into a lightweight, **distroless** container which is more secure and faster to deploy.

Once your Dockerfile is ready, you can build and run your image:

```plaintext
docker build -t news-demo-app:v1 .
```

```plaintext
docker run -p 3000:3000 go-web-app:v1
```

Visit [`localhost:3000`](http://localhost:8080/courses) and you'll see your app running in the container!

## Step 2 : Creating Kubernetes Manifests

Next we will create kubernetes manifests, including deployment, service and ingress configurations. This will allow us to deploy our application on a kubernetes cluster effectively. The deployment manifests will specify the number of replicas, container image and ports.

Deployment YAML

```yaml
  apiVersion: apps/v1
kind: Deployment
metadata:
  name: news-demo-app
  labels:
    app: news-demo-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: news-demo-app
  template:
    metadata:
      labels:
        app: news-demo-app
    spec:
      containers:
      - name: news-demo-app
        image: samgawali/news-demo-app:v3
        ports:
        - containerPort: 3000
```

Service YAML

A **Service** allows your app to be discoverable

```yaml
apiVersion: v1
kind: Service
metadata:
  name: news-demo-app
spec:
  selector:
    app: news-demo-app
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 3000
    protocol: TCP
```

Ingress YAML

while an **Ingress** helps map external traffic into your cluster’s services.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: news-demo-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /

spec:
  ingressClassName: nginx
  rules:
  - host: "*.samgawali.com"
    http:
      paths:
      - pathType: Prefix
        path: "/"
        backend:
          service:
            name: news-demo-app
            port:
              number: 80
```

At this stage, your app is ready for deployment into a Kubernetes cluster.

## Step 3 : Creating a Kubernetes Cluster

For production-grade deployments, we'll set up an Civo Kubernetes Cluster

%[https://www.civo.com/] 

Install the necessary CLI tools including **kubectl**, ***Civo CLI***

Authenticate your Civo account using api key

```plaintext
civo apikey add key-name “Your Civo API Key“
```

Create the cluster

```plaintext
civo kubernetes create demoapp-cluster --size "g4s.kube.medium" --nodes 2 --wait --save --merge --switch --region FRA1 -r=Traefik -a=Nginx --create-firewall --firewall-rules "80,443:0.0.0.0/0"
```

Once done, use `kubectl` to deploy the Kubernetes manifests you created earlier. A simple `kubectl apply` command will do the trick:

```plaintext
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml
```

Be sure to expose the service using a NodePort or Load Balancer for external access!

**Ingress Controller Installation & Configuration**

```plaintext
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml
```

This will install nginx ingress controller in ingress-nginx namespace

A **Loadbalancer** will be created in Civo Kubernetes Cluster as shown in below screenshot

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1729603046183/dcd0e57a-6426-48fd-8bfd-beac5f55c9d8.png align="center")

**DNS Mapping**

We will map our domain to the application using DNS. This will allow users to access the application via a friendly url instead of an ip address. I am using Civo DNS in Civo dashboard to configure my website as DSN for our application.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1729603474263/cb2ce8f1-b5a7-49fd-aed4-bacdde710b6f.png align="center")

## Step 4 : Setting Up Helm Chart

Helm simplifies managing your Kubernetes configs by allowing you to easily deploy to different environments (e.g., Dev, QA, Prod). If you need to deploy the same app across multiple environments with minimal changes, Helm is your go-to tool.

To get started, create a Helm chart:

```plaintext
helm create news-demo-app-chart
```

Now delete all sample files in templates folder and copy all our app Kubernetes manifests files into it for details you can refer to my git repository.

Then, define the values for each environment in **values.yaml file**

Test if generated helm chart is perfect or not with below commands

```plaintext
helm lint ./news-demo-app-chart    

helm template ./news-demo-app-chart 

helm install news-demo-app ./news-demo-app-chart --dry-run
```

Deploy the Helm chart with a command :

```plaintext
helm install go-web-app ./go-web-app-chart
```

## Step 5 : Implementing Continuous Integration (CI) with GitHub Actions

GitHub Actions is a powerful tool to automate your development workflows. Here's how you set up CI for the app:

### Create a GitHub Actions Workflow

Under `.github/workflows/ci.yml`, implement several stages:

* **Build and Test**: Automatically build the Go app whenever code is pushed.
    
* **Test**: Test Go app
    
* **Docker Image Build & Push**: After tests pass, push the Docker image to Docker Hub.
    
* **Update Helm**: Push the updated Helm values file with the latest Docker image.
    

Here’s a snippet from the workflow:

```yaml
# CICD using GitHub actions

name: CI/CD

# Exclude the workflow to run on changes to the helm chart
on:
  push:
    branches:
      - main
    paths-ignore:
      - 'helm/**'
      - 'k8s/**'
      - 'README.md'

jobs:

  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Set up Go 1.23
      uses: actions/setup-go@v2
      with:
        go-version: 1.23

    - name: Build
      run: go build 

    
  
  code-quality:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Test
      run: go test
      
      
  
  push:
    runs-on: ubuntu-latest

    needs: build

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Set up Docker Buildx
      uses: docker/setup-buildx-action@v1

    - name: Login to DockerHub
      uses: docker/login-action@v3
      with:
        username: ${{ secrets.DOCKERHUB_USERNAME }}
        password: ${{ secrets.DOCKERHUB_TOKEN }}

    - name: Build and Push action
      uses: docker/build-push-action@v6
      with:
        context: .
        file: ./Dockerfile
        push: true
        tags: ${{ secrets.DOCKERHUB_USERNAME }}/news-demo-app:${{github.run_id}}

  update-newtag-in-helm-chart:
    runs-on: ubuntu-latest

    needs: push

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      with:
        token: ${{ secrets.TOKEN }}

    - name: Update tag in Helm chart
      run: |
        sed -i 's/tag: .*/tag: "${{github.run_id}}"/' helm/news-demo-app-chart/values.yaml

    - name: Commit and push changes
      run: |
        git config --global user.email "sameer@sgawali.com"
        git config --global user.name "sgawali"
        git add helm/news-demo-app-chart/values.yaml
        git commit -m "Update tag in Helm chart"
        git push
```

Now, pushing to the repo triggers automated testing, Docker pushing, and Helm updates.

## Step : 6 Continuous Delivery (CD) with Argo CD

Argo CD provides a seamless way to implement **GitOps**, allowing automatic updates to your Kubernetes cluster every time there’s a new version.

### Install Argo CD

Run the following to install Argo CD in your Kubernetes cluster:

```yaml
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
```

### Set Up Argo CD

Install a Helm chart directly through Argo CD by creating a new app in the Argo CD dashboard:

1. Set the project to "default."
    
2. Connect to the GitHub repository.
    
3. Specify the path to your Helm chart and set the sync policy to **Automatic.**
    

Now, Argo CD will monitor the Helm chart for new changes and deploy updated versions without any manual intervention!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1729604648971/82dfa61d-db06-4bf1-8935-f8bd26cacc8a.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1729604746618/bd7aa543-460d-49da-83b6-f2b8a8fb86ae.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1729604765678/54100718-6fde-41ca-932e-ee0f4db1f40e.png align="center")

As shown in Screenshots our News demo application is now accessible on my websites for which i have created DNS records. A change in app’s source code will automatically trigger Ci/CD pipelines and changes will be deployed automatically by Argo CD tool.

## Conclusion

When you take a non-DevOps project and transform it into a fully automated, scalable, and reliable system, the possibilities are endless. We started with a basic Go lang demo news app and implemented a full DevOps cycle: containerizing the app, deploying it in Kubernetes with Helm, and automating continuous integration and deployment using GitHub Actions and Argo CD.

Now, it’s time for you to give it a shot! Check out the project repositories, follow the steps, and "DevOpsify" your next project.
