> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Vault PHP Application

> Demonstrates deploying a PHP app on Kubernetes that reads secrets injected into pod filesystem by HashiCorp Vault and displays them in a minimal UI

This guide shows a simple PHP application that reads secrets injected into the pod filesystem by HashiCorp Vault (via Vault Agent Injector) and displays them in a minimal UI. It walks through cloning the repository, building the Docker image, deploying to Kubernetes, patching the Deployment with Vault annotations, and verifying that the app can read the injected secrets.

Repository: [https://github.com/sidd-harth/php-vault-example](https://github.com/sidd-harth/php-vault-example)

Quick start — clone, build, and prepare

```bash theme={null}
git clone https://github.com/sidd-harth/php-vault-example.git
cd php-vault-example
docker build -t php:vault .
```

Why this example

* Demonstrates secret injection into pod filesystem using Vault Agent Injector.
* Shows how a simple PHP app reads files mounted at predetermined paths.
* Useful for learning pod-level secret access patterns and Vault integration with Kubernetes.

Application overview

* The PHP app expects these files (mounted by the Vault injector) at runtime:
  * /vault/secrets/username
  * /vault/secrets/password
  * /vault/secrets/apikey

|         Condition | UI result                                                |
| ----------------: | :------------------------------------------------------- |
|  Any file missing | Red background with "File(s) Not Found"                  |
| All files present | Green background showing Username, Password, and API Key |

Index page (index.php)

* The example `index.php` outputs a small HTML UI and reads the three files under `/vault/secrets/`. It suppresses warnings from `file_get_contents()` using `@` for this demo; in production handle errors explicitly.

```php theme={null}
<?php
// Image reference removed from this example to keep the snippet self-contained.
echo "<table border='3'>
        <tr>
            <th><h2>Course</h2></th>
            <th><h2>Demo</h2></th>
        </tr>
        <tr>
            <td><h3><a href=\"https://learn.kodekloud.com/user/courses/devsecops-kubernetes-devops-security\">DevSecOps - Kubernetes, DevOps & Security</a></h3></td>
            <td><h3>HashiCorp Vault - Secret Injection through SideCar</h3></td>
        </tr>";
echo "</table><br/>";

$username = @file_get_contents("/vault/secrets/username");
$password = @file_get_contents("/vault/secrets/password");
$apikey   = @file_get_contents("/vault/secrets/apikey");

if ($username === false || $password === false || $apikey === false) {
    // There is an error opening one or more files
    echo "<body style='background-color:red'>";
    echo '<h1 style="border: 2px solid DodgerBlue;color:white;"> File(s) Not Found </h1><br/>';
} else {
    echo "<body style='background-color:lightgreen'>";
    echo "<table border='2'>
            <tr>
                <th><h3>Username</h3></th>
                <th><h3>" . htmlspecialchars($username) . "</h3></th>
            </tr>
            <tr>
                <th><h3>Password</h3></th>
                <th><h3>" . htmlspecialchars($password) . "</h3></th>
            </tr>
            <tr>
                <th><h3>API Key</h3></th>
                <th><h3>" . htmlspecialchars($apikey) . "</h3></th>
            </tr>
          </table><br/>";
}
?>
```

HTML/CSS for the page

* The app includes a small style block for the page layout and table formatting:

```html theme={null}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<style>
h1 {
    text-align: center;
    font-family: Bahnschrift SemiBold;
}
table, td, th {
    border: 1px solid black;
    height: 50px;
}
table {
    border-collapse: collapse;
    width: 100%;
}
.center {
    display: block;
    margin-left: auto;
    margin-right: auto;
    width: 50%;
}
td {
    text-align: center;
    height: 50px;
    vertical-align: middle;
    font-family: Bahnschrift SemiBold;
}
tr {
    text-align: center;
    height: 50px;
    vertical-align: middle;
    font-family: Bahnschrift SemiBold;
}
</style>
</head>
```

Dockerfile

* Builds the PHP/Apache container and copies the app into the web root.

```dockerfile theme={null}
FROM php:7.4-apache
COPY src/ /var/www/html
EXPOSE 80
```

Kubernetes manifest

* The provided manifest creates a Deployment (replicas: 1), a NodePort Service, and a ServiceAccount named `app`. The container image referenced is `php:vault` (the image you built locally above).

```yaml theme={null}
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: php
  name: php
spec:
  replicas: 1
  selector:
    matchLabels:
      app: php
  strategy: {}
  template:
    metadata:
      labels:
        app: php
    spec:
      serviceAccountName: app
      containers:
      - image: php:vault
        name: php
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: php
  name: php
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: php
  type: NodePort
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app
  labels:
    app: php
```

Patch step (apply Vault Agent Injector annotations)

* The repository includes `patch-annotations-template.yaml` to add Vault Agent Injector annotations to the Deployment. Apply the base manifest first, then patch the deployment with the annotations so Vault can inject the secrets.

Example commands:

```bash theme={null}
kubectl apply -f php-app-k8s-deploy.yaml
kubectl patch deploy php -p "$(cat patch-annotations-template.yaml)"
```

Deploy and verify

1. Apply the manifest and patch the Deployment:

```bash theme={null}
kubectl apply -f php-app-k8s-deploy.yaml
kubectl patch deploy php -p "$(cat patch-annotations-template.yaml)"
```

2. Confirm the pods, services, and service accounts exist:

```bash theme={null}
kubectl get pods
# Example output:
# NAME                                 READY   STATUS    RESTARTS   AGE
# php-5bc8df55fb-c7tj6                 1/1     Running   0          10s
# vault-0                              1/1     Running   0          55m
kubectl get svc
# Example output:
# NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
# php                          NodePort    10.96.105.209    <none>        80:30835/TCP                 20s
# vault                        ClusterIP   10.109.140.61    <none>        8200/TCP,8201/TCP           56m
kubectl get sa
# Example output:
# NAME                     SECRETS   AGE
# app                      1         32s
# default                  1         57m
# vault                    1         56m
```

Accessing the UI

* The php Service is exposed as a NodePort. Use the node (VM) public IP and NodePort to open the app in your browser:

```text theme={null}
http://<NODE_PUBLIC_IP>:<NODE_PORT>
# e.g. http://40.76.109.0:30835
```

* Behavior:
  * If Vault has not injected the three files at `/vault/secrets/`, the UI shows a red background with "File(s) Not Found".
  * Once Vault Agent Injector mounts/writes the secrets into `/vault/secrets/` inside the pod, the UI will switch to green and display the username, password, and API key.

Pod-level verification

* Inspect the pod filesystem to validate whether the secret files exist:

```bash theme={null}
kubectl get pods
kubectl exec -it <php-pod-name> -- ls /vault/secrets
# If files are missing you'll see: ls: cannot access '/vault/secrets': No such file or directory
# If files exist you will see: username  password  apikey
```

* If files are missing, the UI remains in the red "File(s) Not Found" state. After successful injection, the UI displays secrets (as shown in index.php).

Security notes

* The demo uses the PHP `@` operator to suppress file warnings; prefer explicit error handling in production.
* Use `htmlspecialchars()` (as done here) or other sanitization to avoid HTML injection when rendering secrets in a browser. For production, avoid rendering raw secrets in UI and use secure secrets handling patterns.

<Callout icon="lightbulb" color="#1CB2FE">
  Make sure the mount path used by your Vault injection configuration matches the file paths the application expects (here: /vault/secrets/username, /vault/secrets/password, /vault/secrets/apikey). Also confirm the ServiceAccount used by the Deployment has the necessary annotations and role bindings for Vault Agent Injector to work.
</Callout>

Next steps and references

* Configure and review the Vault Agent Injector annotations in the patch file to write secrets into the pod filesystem.
* Consider using environment variables or in-memory secret stores instead of writing secrets to disk for stronger security.
* Useful links:
  * HashiCorp Vault: [https://www.vaultproject.io/](https://www.vaultproject.io/)
  * Vault Kubernetes integration: [https://www.vaultproject.io/docs/platform/k8s](https://www.vaultproject.io/docs/platform/k8s)
  * Kubernetes documentation: [https://kubernetes.io/docs/](https://kubernetes.io/docs/)

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/devsecops-kubernetes-devops-security/module/baf5859d-32c2-4e7c-9808-f3486d6b9827/lesson/f840eabb-c3ee-43a1-9f04-cd1b70ba3757" />
</CardGroup>
