How to Run an NGINX Web Server
Deploying an NGINX web server is a common way to test deploying an application or service into Kubernetes. While NGINX is straightforward to run and offers a ready-to-use Docker image, deployments can sometimes fail in restricted environments.
This guide provides step-by-step instructions to ensure your NGINX deployment works on the first try.
Privileged vs. Unprivileged
This Kubernetes platform is multitenant, meaning it is shared among many users. To maintain security, privilege escalation is disabled. You must configure a security context to run containers in unprivileged mode.
Instead of building a custom image to meet these restrictions, you can use the official unprivileged NGINX image.
- Image Reference:
nginxinc/nginx-unprivileged - This image is fully compatible with our platform as-is and requires no modifications.
Running User ID
The security context requires a specific user ID and group ID (configured via the runAsUser and runAsGroup fields). For the nginx-unprivileged image, the correct value for both is 101.
You can verify this using docker:
docker run -it --rm --entrypoint /bin/sh nginxinc/nginx-unprivileged
$ id
uid=101(nginx) gid=101(nginx) groups=101(nginx)
$This example assumes the image contains /bin/sh and id binaries. Mainly containerized Go Lang applications usualy contain only the Go application and nothing else. In such a case, Dockerfile used for image build or project documentation must be consulted.
Deployment Manifest
Use the following manifest to deploy your NGINX instance.
- Name: The name
nginxcan be replaced with any valid string (lowercase letters, numbers, dashes). - Resources: Adjust the memory and CPU requests/limits according to your expected usage.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
securityContext:
fsGroupChangePolicy: OnRootMismatch
fsGroup: 101
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginxinc/nginx-unprivileged
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 101
runAsGroup: 101
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: 2
memory: 8GiAdding Persistent Storage
The deployment above works, but it only serves the default NGINX “hello” page. The container’s web root (/usr/share/nginx/html) is not writable by default, meaning you cannot copy data into it after it starts.
The optimal way to serve your own content is by attaching a Persistent Volume Claim (PVC). Read more about PVC.
- Create the PVC
First, create an ad-hoc PVC using the following manifest. Data on this PVC is preserved until the PVC is explicitly deleted.
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-nginx
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Gi
storageClassName: nfs-csi- Update the Deployment
Once the PVC is created, update your deployment manifest to mount the volume to the NGINX web root: Data on this PVC are preserved until the PVC is deleted.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
securityContext:
fsGroupChangePolicy: OnRootMismatch
fsGroup: 101
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: nginx
image: nginxinc/nginx-unprivileged
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 101
runAsGroup: 101
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: 2
memory: 8Gi
volumeMounts:
- name: vol-1
mountPath: /usr/share/nginx/html
volumes:
- name: vol-1
persistentVolumeClaim:
claimName: test-nginxThe PVC name defined in pvc.yaml (name: test-nginx) must exactly match the claimName (claimName: test-nginx).
Directory indexing is disabled by default in NGINX. If you do not upload an index.html file to the PVC, opening the URL in a browser will return a 403 Forbidden error.
Exposing the NGINX Server
To make the running NGINX instance accessible from the internet, you need to expose it. For deeper details, see the Exposing applications section.
You will need two additional manifests: a Service and an Ingress.
Service
The Service routes internal traffic to your running Pod. It relies on the application label (app: nginx in our deployment) and the target port. The nginx-unprivileged image listens on port 8080 by default. See nginx-unprivileged documentation.
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 8080
targetPort: 8080
protocol: TCP
selector:
app: nginxIngress
The Ingress manifest configures the external hostname and provisions a Let’s Encrypt TLS certificate for secure HTTPS access.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/tls-acme: "true"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- "[some-name].dyn.cloud.e-infra.cz"
secretName: [some-name]-dyn-cloud-e-infra-cz
rules:
- host: [some-name].dyn.cloud.e-infra.cz
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: nginx
port:
number: 8080Configuration Notes
Replace the [some-name] with a unique identifier for your project. Do not use generic names such as test, example, as they are likely already taken.
The backend.service.name and port.number in the Ingress manifest must exactly match the name and port defined in your Service object.
Last updated on
