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-exampleQuick start — clone, build, and prepare
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.
Copy
<?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:
Builds the PHP/Apache container and copies the app into the web root.
Copy
FROM php:7.4-apacheCOPY src/ /var/www/htmlEXPOSE 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).
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.
Confirm the pods, services, and service accounts exist:
Copy
kubectl get pods# Example output:# NAME READY STATUS RESTARTS AGE# php-5bc8df55fb-c7tj6 1/1 Running 0 10s# vault-0 1/1 Running 0 55mkubectl 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 56mkubectl 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:
Copy
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:
Copy
kubectl get podskubectl 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.
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.
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.