In a previous article, we introduced a DevOps API designed to support dynamic Jenkins parameters using Active Choices and Groovy. That initial version focused on exposing data from internal systems such as GitLab and PostgreSQL.

However, before introducing Swagger, it is important to understand the foundational work that enabled this evolution.

This was not a greenfield project. Instead, Swagger was introduced into an already functional, containerized, and deployed API running in Kubernetes.


Building the Foundation

To simulate a realistic DevOps environment and support future use cases, we first created a set of GitLab repositories representing different services:

Each repository was populated with approximately 20 semantic version tags. These tags would later serve as the primary data source for our API endpoints.


Private Container Registry (Nexus)

To support containerized deployments, we created a dedicated Docker repository in Nexus: devops_images

This repository stores all application images, including the DevOps API itself.


Fixing Internal TLS Trust

Since the environment relies on internal certificate authorities, we needed to ensure that containers could trust internal services such as GitLab and Nexus.

This required updating the base image used by our applications:

  • adding the internal Root and Intermediate CA certificates
  • installing them into the system trust store
  • running update-ca-certificates

This step was critical to allow secure communication without relying on insecure flags such as -k.

curl -u usr_jenkins_nexus:1234qwer -k https://nexus.devops-db.internal/repository/infrastructure/pki/intermediate/devops-db-intermediate.crt -o devops-db-intermediate.crt
curl -u usr_jenkins_nexus:1234qwer -k https://nexus.devops-db.internal/repository/infrastructure/pki/root/devops-db-root-ca.crt -o devops-db-root-ca.crt
cat devops-db-intermediate.crt devops-db-root-ca.crt > ca.crt

https://github.com/faustobranco/devops-db/tree/master/Initial%20Images/go

FROM ubuntu:22.04

USER root

ARG TARGETARCH
ENV WORKDIR=/pipeline
ENV DEBIAN_FRONTEND=noninteractive
RUN mkdir -p ${WORKDIR}
WORKDIR ${WORKDIR}
ENV GO_VERSION=1.23.0

COPY ca.crt /usr/local/share/ca-certificates/devops-db.crt

RUN apt-get clean \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get update --fix-missing \
    && apt-get install -y --no-install-recommends \
       ca-certificates \
       wget \
       vim \
       curl \
       less \
       git \
       openjdk-21-jre-headless \
    && update-ca-certificates \
    && rm -rf /var/lib/apt/lists/*

RUN wget https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz \
    && tar -C /usr/local -xzf go${GO_VERSION}.linux-${TARGETARCH}.tar.gz \
    && rm go${GO_VERSION}.linux-${TARGETARCH}.tar.gz

ENV GOPATH=/go
ENV PATH=$PATH:/usr/local/go/bin:$GOPATH/bin

RUN mkdir -p $GOPATH/bin && chmod -R 777 $GOPATH


Building the Application Image

With the base image fixed, we created the Docker image for the DevOps API using a multi-stage build:

  • build stage using Go
  • runtime stage based on the internal base image

The resulting image was pushed to Nexus: nexus.devops-db.internal/devops_images/devops-api:<version>

# build stage
FROM golang:1.26-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o devops-api
#RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o devops-api

# runtime stage
FROM nexus.devops-db.internal/base_images/base_go:1.0.4

WORKDIR /app

COPY --from=builder /app/devops-api .

EXPOSE 8080

CMD ["./devops-api"]

https://github.com/faustobranco/devops-db/tree/master/infrastructure/resources/devops-api/docker


Kubernetes Deployment with Helm

The application was deployed to Kubernetes using Helm, ensuring a reproducible and configurable deployment.

The Helm chart included:

  • Deployment
  • Service
  • ConfigMap
  • Secrets
  • Gateway API resources

https://github.com/faustobranco/devops-db/tree/master/infrastructure/resources/devops-api/helm/devops-api


Exposing the API with Gateway API and Traefik

Instead of using traditional Ingress, we adopted the Kubernetes Gateway API with Traefik as the controller.

This provided:

  • more explicit routing configuration
  • better separation of concerns
  • future-proof networking model

The API was exposed using a dedicated hostname: devops-api.devops-db.internal

Configured via DNS:

devops-api IN A 172.21.5.240

Database Access

To support endpoints related to databases and schemas, we configured PostgreSQL access:

CREATE USER devops_api WITH PASSWORD '1234qwer';
ALTER USER "devops_api" WITH CREATEDB;
GRANT CONNECT ON DATABASE postgres TO devops_api;

This allowed the API to query database metadata dynamically.


Deployment and Validation

Once all components were in place, the application was deployed to Kubernetes and validated through direct API calls.

At this stage, the API already supported endpoints such as:

  • /health
  • /tags
  • /databases
  • /schemas
  • /blueprints/*

For example:

curl -s "http://devops-api.devops-db.internal/tags?repo=services/reporting&sort=date"

returned a list of semantic version tags retrieved directly from GitLab.

curl -s "http://devops-api.devops-db.internal/tags?repo=services/reporting&sort=date" | jq
[
  "v0.1.0",
  "v0.1.1",
  "v0.2.0",
  "v0.2.1",
  "v0.2.2",
  "v0.3.0",
  "v0.3.1",
  "v0.4.0",
  "v0.4.1",
  "v0.5.0",
  "v0.6.0",
  "v0.7.0",
  "v0.8.0",
  "v0.9.0",
  "v0.9.1",
  "v1.0.0",
  "v1.1.0",
  "v1.2.0",
  "v1.2.1",
  "v1.3.0"
]


curl -s "http://devops-api.devops-db.internal/schemas?service=reporting&env=dev&db=devops_test_db" | jq

{
  "status": "ok",
  "data": [
    "infrastructure_tests",
    "public"
  ]
}