Skip to main content

Deploy Your First App using Gappynator and Argo CD

This guide walks you through deploying your first application to GAP using Gappynator and Argo CD. By the end, you will have a GitHub Actions workflow that builds and pushes a container image, updates your Kubernetes manifests, and triggers a GitOps-based deployment via Argo CD.

Prepare Your Dockerfile

Your GitHub repository must contain a Dockerfile that builds your application image.

Set Up Repo Access to ACR

In order to push images to ACR, you will need to add your repo to terraform-github-repo-credentials. Create a PR that adds your repo to your team's tf file team_<your-team>.tf located at repos-container-registry-push.

Once merged and applied by the Platform team, the secret AZURE_CONTAINER_REGISTRY_CLIENT_ID will be provisioned in your repo and can be used to push images using GitHub Actions.

GitHub Actions Workflow for Building and Pushing Images to ACR

We will use our custom action docker-build-scan-push-action to build and push to a container registry.

.github/workflows/build_and_deploy.yaml
name: "Build and Push to ACR"

on:
push:
branches:
- "main"

env:
TEAM_NAME: "my-team-name" # Change this

permissions:
id-token: write
contents: read
security-events: write
attestations: write

jobs:
build:
runs-on: "ubuntu-latest"
environment: "azure" # Must match one of the environments added in "terraform-github-repo-credentials"
steps:
- name: "Git checkout"
uses: "actions/checkout@v2"

# Depending on your Dockerfile, you might have to build your application before proceeding

- name: "Build, Scan and Push Docker image"
id: "build-scan-push"
uses: "gjensidige/docker-build-scan-push-action@main"
with:
team-name: ${{ env.TEAM_NAME }}
tag: ${{ github.sha }}
azure-client-id: ${{ secrets.AZURE_CONTAINER_REGISTRY_CLIENT_ID }}

# Use container image output to proceed performing GitOps actions
# Image output is on the format: "gjensidige.azurecr.io/your-team-name/github-repo-name:tag"
- name: "Example using container image output"
run: echo ${{ steps.build-scan-push.outputs.image }}

For more details view guide for pushing container images.

Repo GitHub Token

Create another PR in terraform-github-repo-credentials that adds your repo under repos-github-token - an example PR can be found here.

After merging, the secret <TEAM_NAME>_KUBERNETES_MANIFESTS_TOKEN will be added to your repo secrets.

For details you can check this README.

Gappynator Manifest

We will be using Gappynator, a custom operator that makes it easier to deploy services in GAP, ensures best practices and automates many steps.

info

Gappynator documentation is coming soon — a link will be added here when available.

In this guide we will use jsonnet for the Gappynator manifest.

You can learn more about jsonnet in our guide.

Add the manifest to your team's Kubernetes manifests repo, which follows the naming convention <your-team>-kubernetes-manifests.

app.jsonnet
local params = import 'params.json';

local environment = 'test'; // value set to 'test' or 'prod'
local name = <app_name>;
local namespace = <your_namespace>;
local image = params.container_image;
local github_labels = params.github;

local labels = {
['github.gap.io/' + key]: std.get(github_labels, key, null)
for key in std.objectFields(std.get({ github_labels: github_labels }, 'github_labels', {}))
} + {
'gap.io/owner': <owner>,
'gap.io/cost-center': <cost-center>,
'gap.io/service-code': <service-code>,
environment: environment,
};

local appPort = <application_port>;

local app = {
apiVersion: 'gap.io/v1',
kind: 'Application',
metadata: {
labels: labels,
name: name,
annotations: {},
namespace:: namespace,
},
spec: {
image: image,
port: appPort,
},
};

{
apiVersion: 'v1',
kind: 'List',
items: [app],
}

Each environment will have a subfolder with manifests and a params.json file:

params.json
{
"container_image": "",
"container_image_tag": "",
"github": {
"workflow_actor_username": "",
"repo_name": "",
"repo_commit_sha": "",
"manifest_repo_name": "",
"manifest_repo_commit_sha": ""
}
}

The values in params.json will be automatically patched by the deploy workflow after each successful build, as described in the next step.

Update Your Workflow to Trigger Deployment

Now that your manifests are in place, extend the workflow from the earlier step with an additional GAP Workflow Dispatch step. This step patches params.json with the correct container image and metadata after each build, which Argo CD then picks up to sync the cluster to the desired state.

.github/workflows/build_and_deploy.yaml
name: "Build and Push to ACR"

on:
push:
branches:
- "main"

env:
TEAM_NAME: "my-team-name" # Change this

permissions:
id-token: write
contents: read
security-events: write
attestations: write

jobs:
build:
runs-on: "ubuntu-latest"
environment: "azure" # Must match one of the environments added in "terraform-github-repo-credentials"
steps:
- name: "Git checkout"
uses: "actions/checkout@v2"

# Depending on your Dockerfile, you might have to build your application before proceeding

- name: "Build, Scan and Push Docker image"
id: "build-scan-push"
uses: "gjensidige/docker-build-scan-push-action@main"
with:
team-name: ${{ env.TEAM_NAME }}
tag: ${{ github.sha }}
azure-client-id: ${{ secrets.AZURE_CONTAINER_REGISTRY_CLIENT_ID }}

# Use container image output to proceed performing GitOps actions
# Image output is on the format: "gjensidige.azurecr.io/your-team-name/github-repo-name:tag"
- name: "Example using container image output"
run: echo ${{ steps.build-scan-push.outputs.image }}

# Dispatch GAP workflow
- name: "GAP Workflow Dispatch"
uses: gjensidige/gap-workflow-dispatch-action@main
with:
token: ${{ secrets.KUBERNETES_MANIFESTS_TOKEN }}
repository: <manifest-repo-name> # replace with manifest repo name
environment: <environment> # replace with your environment
deployment-type: backend
container-image: ${{ steps.build-scan-push.outputs.image }}

Argo CD Manifest

Now in order for Argo CD to watch and sync the changes applied to our manifests, we need to create an Argo CD "Application" manifest.

application.jsonnet
local app = import '../../../../templates/app.libsonnet';

app {
// replace with your own values
name:: '<your-app-name>',
namespace:: '<your-namespace>',
team:: '<your-team>',
project:: '<your-project>',
environment:: '<environment>',
type:: 'infrastructure',
sources:: [
{
repoURL: '<manifests-repo-link>', // replace with your Kubernetes manifests repo URL
targetRevision: 'main',
path: 'apps/<app-name>/<env-subdirectory>', // replace with the path of your deployment manifests
directory: {
// Must be excluded as it is not a valid manifest
exclude: 'params.json',
jsonnet: {
libs: [
'vendor',
],
},
},
},
],
auto_sync+:: {
enabled: true,
},
}

For more details, follow our guide on Argo CD here.

Verify Application is Running

Once the Argo CD Application manifest has been applied, log in to Argo CD for your target environment and confirm your application shows a health status of Healthy and a sync status of Synced.