Skip to content

Example: Deploying a Three-Tier Web Application

This document walks through a complete example of a typical three-tier web application (frontend, backend, database). It is not a step-by-step tutorial but rather a case study demonstrating how the platform's features and best practices work together.

This guide assumes you are familiar with the concepts covered in our other "how-to" guides. Before proceeding, we recommend you review:

Application Architecture

We will be deploying an application with the following architecture. An HTTPProxy resource exposes the frontend to the internet. The frontend communicates with the backend, which in turn communicates with a managed PostgreSQL database. All components run within the same, secured namespace.

graph TD
    subgraph Internet
        User[User]
    end

    subgraph "The Contain Platform"
        subgraph "Kubernetes Cluster"
            subgraph "Application Namespace"
                Ingress(HTTPProxy)
                FrontendSvc[Frontend Service]
                FrontendDeploy[Frontend Deployment]
                BackendSvc[Backend Service]
                BackendDeploy[Backend Deployment]
                DatabaseCR(Database CR)
            end
        end
        ManagedDB[(Managed<br>PostgreSQL<br>Instance)]
    end

    User --> Ingress;
    Ingress --> FrontendSvc;
    FrontendSvc --> FrontendDeploy;
    FrontendDeploy --> BackendSvc;
    BackendSvc --> BackendDeploy;
    BackendDeploy --> ManagedDB;
    DatabaseCR -.-> ManagedDB;
Hold "Alt" / "Option" to enable pan & zoom

1. Provisioning the Database

First, we provision a PostgreSQL database using the managed Databases Service. We create a Database custom resource, which tells the platform's db-operator to create a new database and a secret containing the credentials.

For more details, see the Getting Started with Databases guide.

apiVersion: "kinda.rocks/v1beta1"
kind: "Database"
metadata:
  name: "three-tier-app-db"
spec:
  # Name of the secret the operator will create
  secretName: db-credentials
  # Name of the pre-existing database server instance
  instance: prod1-dc4-dbaas01.netic-platform.shared.k8s.netic.dk
  deletionProtected: true
  backup:
    enable: false

2. Deploying the Backend

The backend is a stateless application that connects to the database. Its Deployment manifest includes several key configurations:

  • A compliant securityContext, as required by the platform.
  • An envFrom block to consume the database credentials from the Secret created in Step 1.

For more details, see the Managing Application Secrets and Configuring Pod Security Contexts guides.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  labels:
    app.kubernetes.io/name: backend
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: backend
  template:
    metadata:
      labels:
        app.kubernetes.io/name: backend
    spec:
      securityContext:
        runAsUser: 1001
        runAsGroup: 1001
        fsGroup: 1001
        runAsNonRoot: true
      containers:
        - name: backend
          image: your-registry/backend:v1.0.0
          ports:
            - containerPort: 8080
              name: http
          # Consume the database credentials as environment variables
          envFrom:
            - secretRef:
                name: db-credentials
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]

3. Deploying the Frontend

The frontend is another stateless application. Its Deployment is similar to the backend's but includes an additional label, netic.dk/network-ingress: "contour", which is required to allow traffic from the ingress controller.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  labels:
    app.kubernetes.io/name: frontend
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: frontend
  template:
    metadata:
      labels:
        app.kubernetes.io/name: frontend
        # This label is required to receive traffic from the ingress controller
        netic.dk/network-ingress: "contour"
    spec:
      securityContext:
        runAsUser: 1002
        runAsGroup: 1002
        fsGroup: 1002
        runAsNonRoot: true
      containers:
        - name: frontend
          image: your-registry/frontend:v1.0.0
          ports:
            - containerPort: 8080
              name: http
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: ["ALL"]

4. Enabling Network Communication

By default, the platform's network is "default-deny." To allow the frontend and backend to communicate, we create Service resources for each and a LocalNetworkConfig to define the allowed traffic path.

For more details, see the Configuring Application Networking guide.

---
# Service for the Frontend
apiVersion: v1
kind: Service
metadata:
  name: frontend-svc
spec:
  selector:
    app.kubernetes.io/name: frontend
  ports:
    - protocol: TCP
      name: http
      port: 80
      targetPort: 8080
---
# Service for the Backend
apiVersion: v1
kind: Service
metadata:
  name: backend-svc
spec:
  selector:
    app.kubernetes.io/name: backend
  ports:
    - protocol: TCP
      name: http
      port: 80
      targetPort: 8080
---
# LocalNetworkConfig to allow frontend to talk to backend
apiVersion: networking.tcs.trifork.com/v1alpha1
kind: LocalNetworkConfig
metadata:
  name: frontend-to-backend-policy
spec:
  components:
    frontend:
      podSelector:
        matchLabels:
          app.kubernetes.io/name: frontend
      dependsOn:
        - component: backend
          port: 80
    backend:
      podSelector:
        matchLabels:
          app.kubernetes.io/name: backend

5. Exposing the Application to the Internet

Finally, we expose the frontend to the internet using the recommended approach of an HTTPProxy resource for traffic routing and a Certificate resource for automatic TLS.

For more details, see the Exposing Applications to the Internet guide.

---
# First, request the TLS certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: three-tier-app-certificate
spec:
  secretName: three-tier-app-tls
  dnsNames:
  - my-three-tier-app.your-domain.com
  issuerRef:
    name: letsencrypt
    kind: ClusterIssuer
---
# Next, create the HTTPProxy to route traffic
apiVersion: projectcontour.io/v1
kind: HTTPProxy
metadata:
  name: three-tier-app-httpproxy
spec:
  virtualhost:
    fqdn: my-three-tier-app.your-domain.com
    tls:
      secretName: three-tier-app-tls
  routes:
    - conditions:
      - prefix: /
      services:
        - name: frontend-svc
          port: 80

With all these components deployed via GitOps, the three-tier application is now securely running, connected, and exposed to the internet, following all platform best practices.