Skip to content

Developer Getting Started Guide

Welcome to the Contain Platform! This guide will walk you through the entire lifecycle of deploying your first application, from source code on your local machine to a running service in a Kubernetes cluster.

By the end of this guide, you will have:

  • Written a simple "Hello World" web application.
  • Containerized the application using Docker.
  • Pushed the container image to a registry.
  • Configured your local environment to connect to your cluster.
  • Structured a GitOps repository.
  • Deployed the application using a GitOps workflow with Flux.
  • Accessed the running application.

1. Create a "Hello World" Application

First, let's create a simple web server application. This example uses Go, but you can use any language.

Create a new directory for your application source code (e.g., hello-app/) and add a file named main.go.

// hello-app/main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        hostname, _ := os.Hostname()
        fmt.Fprintf(w, "Hello from the Contain Platform!\n\nHostname: %s\n", hostname)
    })

    log.Println("Server starting on port 8080...")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

Containerize the Application

Next, create a Dockerfile in the same directory. This file defines the steps to build your Go application into a secure, minimal container image using a multi-stage build.

# hello-app/Dockerfile
# Build Stage
FROM golang:alpine AS builder

WORKDIR /app

# Copy source code and download dependencies
# Create a go.mod file first: `go mod init hello-app`
COPY go.mod ./
RUN go mod download
COPY . .

# Build the static, production-ready binary
RUN CGO_ENABLED=0 GOOS=linux go build -o /hello-app .

# Final Stage
FROM gcr.io/distroless/static-debian11

# Copy the binary from the build stage
COPY --from=builder /hello-app /

# Expose the port the app listens on
EXPOSE 8080

# Set the entrypoint
ENTRYPOINT ["/hello-app"]

Build and Push the Image

Now, build the container image and push it to your container registry.

Docker Registry

You need to have access to a docker registry before you do this. This could be Docker Hub or GitHub Container Registry.

Replace Placeholders

Remember to replace <your-registry>/<your-repo>/hello-app:v0.1.0 with the actual path to your registry and repository.

# Navigate to your application source directory
cd hello-app/

# (If you haven't already) Initialize the Go module
go mod init hello-app
go mod tidy

# Build the image
docker build -t <your-registry>/<your-repo>/hello-app:v0.1.0 .

# Push the image to your registry
docker push <your-registry>/<your-repo>/hello-app:v0.1.0

2. Prerequisites and Preparation

Before you can deploy your new application, you need to configure your local environment to connect to your Kubernetes cluster and set up the GitOps repository that will manage your deployments.

This section is included from our Contain Base documentation and provides the essential setup steps.

Connecting to Your Cluster

The method for connecting to your cluster depends on where the cluster's control plane is hosted.

The GitOps Workflow

While using the Kubernetes API directly is useful for inspection and troubleshooting, the primary way you will manage your applications on the Contain Platform is through Git. See the Deployment Service Introduction for more information.

Making direct changes to the cluster (e.g., with kubectl apply or kubectl edit) is discouraged, as it bypasses the GitOps workflow. This can lead to inconsistencies between the desired state in your repository and the actual state of your applications. Depending on your role, you may only have read-only access to the cluster.

Managed Clusters (Where we manage the control plane)

For clusters where the Contain Platform manages the control plane, your primary entry point for accessing cluster information and your configuration file is through a dedicated Grafana instance.

1. Access Grafana and Download Your kubeconfig

Each customer has a central Grafana instance that provides read-only access to logs and metrics for your applications and clusters, as well as links to other services and configuration files.

Kubeconfig in Grafana

  1. Find Your Grafana URL: The URL for the Grafana instance that shows metrics for your cluster can be found in the Netic Docs space that the cluster belongs to. The general format is https://<provider-name>.dashboard.netic.dk/, where <provider-name> is the name of the provider your clusters exists under.

  2. Log In: Log in to Grafana using your provided credentials.

  3. Locate Your Cluster: Within Grafana, you can view the clusters and namespaces to which you have access. Here you can also find direct links to other services like Vault.

  4. Download kubeconfig: In your cluster's dashboard, locate and use the download link for your Kubernetes configuration (kubeconfig). This file contains the credentials and endpoint information kubectl needs to connect to your cluster's API server.

2. Configure Local Access with kubectl and OIDC

The Contain Platform uses OIDC for secure authentication. To enable this in your local terminal, you need a helper tool called kubelogin.

  1. Install kubelogin: This is a kubectl plugin for OIDC authentication. The easiest way to install it is with a package manager. For example, on macOS with Homebrew:

    brew install int128/kubelogin/kubelogin
    
    For other installation methods, please see the official kubelogin installation guide.

  2. Set Up Your kubeconfig:

    • Place the kubeconfig file you downloaded from Grafana in a secure location (e.g., ~/.kube/).
    • Point the KUBECONFIG environment variable to your downloaded file. It is common practice to add this command to your shell's startup file (e.g., ~/.zshrc, ~/.bash_profile, or ~/.bashrc).
      export KUBECONFIG=~/.kube/my-cluster-config.yaml
      
  3. Log In: The first time you run a kubectl command, kubelogin will automatically open a browser window and prompt you to log in via Keycloak. After you successfully authenticate, it will securely store a token for future kubectl commands.

3. Test Your Connection

To verify that everything is configured correctly, run the following command in your terminal:

kubectl get namespaces

If the connection is successful, you will see a list of the Kubernetes namespaces that you are authorized to view.

Public Cloud Clusters (AWS, Azure, GCP)

For clusters running on a managed Kubernetes service from a public cloud provider, you should follow the provider's official documentation for authenticating and connecting to the cluster. Their command-line tools are designed to handle the creation and management of your kubeconfig file.

Recommended Tool: k9s

While kubectl is the standard command-line tool for interacting with Kubernetes, many users find a terminal-based UI to be more productive for day-to-day tasks.

We highly recommend k9s, a powerful tool that provides an interactive dashboard inside your terminal. It makes it easy to navigate your cluster, view the status of your resources, stream logs, and even exec into pods.

You can find the installation guide here: k9s Installation.

GitOps Preparations: Your Repository

The Deployment Service on the Contain Platform uses a GitOps workflow, which means your Git repository is the single source of truth for your applications.

We can provide you with a Git repository with the necessary credentials to get started, or you can bring your own. The most important first step is to establish a clear and scalable directory structure.

We recommend following the structure advised by the Flux community, which separates concerns between cluster-level configuration and application workloads.

Read the official Flux repository structure guide

A good starting point for your repository structure looks like this:

.
├── clusters
│   └── my-cluster          # Contains the Flux configuration for your cluster
│       └── apps-sync.yaml
└── apps
    └── base                # Contains the manifests for your applications
        ├── app1
        └── app2
  • clusters/: This is the entry point for Flux. It defines which sets of applications should be deployed to a cluster.
  • apps/: This directory contains the actual Kubernetes manifests (Deployment, Service, etc.) for each of your applications.

3. Deploy with GitOps

With your local environment configured and your GitOps repository available, you are ready to deploy. The process follows these steps:

  1. Provision the Namespace: First, you tell our automated Namespace Provisioning service to create a secure, ready-to-use namespace for your application.
  2. Define the Application: You create the standard Kubernetes manifests (Deployment, Service, etc.) that define your application.
  3. Configure the Cluster: You tell the GitOps controller (Flux) to deploy your application by adding it to the cluster's Kustomize configuration.

Step 1: Provision the Namespace

Before deploying your application, you must first create a namespace for it. We manage this through a dedicated Namespace Provisioning service.

You request a new namespace by creating a ProjectBootstrap resource in your GitOps repository. Create the following file:

# clusters/my-cluster/hello-app-project.yaml
apiVersion: project.tcs.trifork.com/v1alpha1
kind: ProjectBootstrap
metadata:
  name: hello-app
  namespace: netic-gitops-system
spec:
  namespace: hello-app
  config:
    ref: default
    size: default
  git:
    branch: main
    path: ./clusters/my-cluster

This manifest tells our namespace operator to create a new namespace called hello-app with a set of secure, default configurations.

Advanced Topic

The ProjectBootstrap resource is a powerful tool for customizing new namespaces. While this example uses the default configuration, you can learn more in the dedicated Namespace Provisioning Service guide.

Step 2: Define the Application Manifests

In your GitOps repository, create a new directory for your application: apps/hello-app/. Inside this directory, create the following Kubernetes manifests.

# apps/hello-app/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-app
  namespace: hello-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-app
  template:
    metadata:
      labels:
        app: hello-app
    spec:
      containers:
      - name: app
        # --- IMPORTANT ---
        # Replace this with the image you pushed
        image: <your-registry>/<your-repo>/hello-app:v0.1.0
        ports:
        - containerPort: 8080
# apps/hello-app/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-app
  namespace: hello-app
spec:
  type: ClusterIP
  selector:
    app: hello-app
  ports:
  - port: 80
    targetPort: 8080

Now, create a kustomization.yaml file in the same directory. This file tells Kustomize (and Flux) which resources are part of the hello-app application.

# apps/hello-app/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml

What about the Namespace?

The ProjectBootstrap resource from Step 1 handles the creation of the namespace for us.

Step 3: Configure the Cluster to Deploy the App

Finally, you need to tell the GitOps controller to deploy both the project and the application. You do this by editing the kustomization.yaml file for your cluster, which is typically located at clusters/my-cluster/kustomization.yaml.

Add your new ProjectBootstrap manifest and the path to your application's manifests to this file.

# clusters/my-cluster/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  # This tells Flux to apply the projectbootstrap manifest
  - hello-app-project.yaml
  # This tells Flux to apply all the manifests in the apps/hello-app directory
  - ../../apps/hello-app

Step 4: Commit and Push

Your final repository structure should look like this:

.
├── apps
│   └── hello-app
│       ├── deployment.yaml
│       ├── kustomization.yaml
│       └── service.yaml
└── clusters
    └── my-cluster
        ├── hello-app-project.yaml
        └── kustomization.yaml

Commit all your new and updated files to the GitOps repository and push the changes.

git add .
git commit -m "Deploy hello-app application"
git push

Flux will now automatically detect the changes. It will first apply the ProjectBootstrap, causing the namespace operator to create and secure the hello-app namespace. Then, Flux will deploy your application's Deployment and Service into that new namespace.

4. Verify and Access Your Application

After a few moments for both the namespace to be created and the application to be deployed, you can verify that everything was successful.

Check the Pods

Use kubectl to check the status of the pods in the new hello-app namespace.

kubectl get pods -n hello-app
You should see two hello-app pods with a STATUS of Running.

Access with Port Forwarding

The Service you created is of type ClusterIP, which means it's only accessible from within the cluster. To access it for testing, you can use kubectl port-forward to create a secure tunnel from your local machine directly to the service.

kubectl port-forward svc/hello-app 8080:80 -n hello-app

Now, open your web browser and navigate to http://localhost:8080. You should see the "Hello from the Contain Platform!" message from your application.

5. Next Steps

Congratulations! You have successfully deployed your first application to the Contain Platform using a secure, automated GitOps workflow.

From here, you can continue your journey by exploring how to operate and debug your new service.

Operating and Observability

To gain deep insights into your application's performance, you will need to configure observability. This involves:

  • Instrumenting your application to expose metrics (e.g., with a Prometheus client library).
  • Creating a ServiceMonitor resource to tell the platform's observability service how to scrape metrics from your application.

For more details, see the Application Observability Service documentation.

Debugging

When you need to troubleshoot a running application, one of the most powerful techniques is to attach a debug container. This allows you to connect a separate container with a full set of debugging tools (like curl, netcat, or a shell) to your running application's pod, without having to include those tools in your production container image.