Nginx

How to deploy an unprivileged Nginx containerized application with score-compose and score-k8s

Overview

In this example we will walk you through how you can deploy an unprivileged Nginx containerized application, and this with both score-compose and score-k8s.

  flowchart TD
    dns[DNS] --> nginx-workload(Nginx)
    subgraph Workloads
        nginx-workload
    end
    nginx-workload-->Volume

1. score.yaml

Open your IDE and paste in the following score.yaml file, which describes a simple web server exposed via a DNS. The demo code can be found here.

apiVersion: score.dev/v1b1
metadata:
  name: nginx
containers:
  webapp:
    image: .
service:
  ports:
    tcp:
      port: 80
      targetPort: 80
resources:
  dns:
    type: dns
  route:
    type: route
    params:
      host: ${resources.dns.host}
      path: /
      port: 80

You can use this Score file with the nginx container image. But a more secure approach is to use the nginxinc/nginx-unprivileged instead. For this we need to anticipate that our Nginx container will run as unprivileged, exposed on port 8080, and will need to write some files in the /tmp folder as a volume:

apiVersion: score.dev/v1b1
metadata:
  name: nginx
containers:
  webapp:
    image: .
    volumes:
    - source: ${resources.tmp}
      target: /tmp
      readOnly: false
service:
  ports:
    tcp:
      port: 8080
      targetPort: 8080
resources:
  tmp:
    type: volume
  dns:
    type: dns
  route:
    type: route
    params:
      host: ${resources.dns.host}
      path: /
      port: 8080

We will use this last Score file for the rest of this page.

From here, you can deploy this exact same Score file:

2. score-compose

To begin, follow the installation instructions to install the latest version of score-compose.

init

Initialize your current score-compose workspace, run the following command in your terminal:

score-compose init --no-sample \
    --patch-templates https://raw.githubusercontent.com/score-spec/community-patchers/refs/heads/main/score-compose/unprivileged.tpl

The init command will create the .score-compose directory with the default resource provisioners available.

You can see the resource provisioners available by running this command:

score-compose provisioners list

The Score file example illustrated uses three resource types: dns, route and volume.

+---------------+-------+------------------+--------------------------------+---------------------------------+
|     TYPE      | CLASS |      PARAMS      |            OUTPUTS             |          DESCRIPTION            |
+---------------+-------+------------------+--------------------------------+---------------------------------+
| dns           | (any) |                  | host                           | Outputs a *.localhost domain    |
|               |       |                  |                                | as the hostname                 |
+---------------+-------+------------------+--------------------------------+---------------------------------+
| route         | (any) | host, path, port |                                | Provisions an Ingress route on  |
|               |       |                  |                                | a shared Nginx instance         |
+---------------+-------+------------------+--------------------------------+---------------------------------+
| volume        | (any) |                  | source, type                   | Creates a persistent volume     |
|               |       |                  |                                | that can be mounted on a        |
|               |       |                  |                                | workload.                       |
+---------------+-------+------------------+--------------------------------+---------------------------------+

By using the --patch-templates (in this case: unprivileged.tpl) we are also making sure that the generated workload will run as unprivileged.

generate

Convert the score.yaml file into a runnable compose.yaml, run the following command in your terminal:

score-compose generate score.yaml --image nginxinc/nginx-unprivileged:alpine-slim

The generate command will add the input score.yaml workload with a particular container image to the .score-compose/state.yaml state file and generate the output compose.yaml.

If you want to build the container image when this compose.yaml will be deployed, you can run this generate command with the --build parameter instead:

score-compose generate score.yaml --build 'main={"context":".","tags":["your-web-app:local"]}'

See the generated compose.yaml by running this command:

cat compose.yaml

If you make any modifications to the score.yaml file, run score-compose generate score.yaml to regenerate the output compose.yaml.

resources

Get the information of the resources dependencies of the workload, run the following command:

score-compose resources list
+---------------------------+--------------+
|            UID            |   OUTPUTS    |
+---------------------------+--------------+
| dns.default#nginx.dns     | host         |
+---------------------------+--------------+
| volume.default#nginx.tmp  | source, type |
+---------------------------+--------------+
| route.default#nginx.route |              |
+---------------------------+--------------+

At this stage, we can already see the value of the dns resource generated:

score-compose resources get-outputs dns.default#nginx.dns --format '{{ .host }}'
dnspb7p6y.localhost

docker compose

Run docker compose up to execute the generated compose.yaml file:

docker compose up -d
[+] Running 5/5
 ✔ Network nginx_default                 Created 
 ✔ Volume "nginx-tmp-cuv0AI"             Created 
 ✔ Container nginx-routing-PFCYHt-1      Started 
 ✔ Container nginx-wait-for-resources-1  Exited 
 ✔ Container nginx-nginx-webapp-1        Started

docker ps

See the running containers:

docker ps
CONTAINER ID   IMAGE                                     COMMAND                  CREATED          STATUS          PORTS                                              NAMES
87ecb7c045c7   nginxinc/nginx-unprivileged:alpine-slim   "/docker-entrypoint.…"   39 seconds ago   Up 37 seconds   8080/tcp                                           nginx-nginx-webapp-1
9b40335c6644   mirror.gcr.io/nginx:1-alpine              "/docker-entrypoint.…"   39 seconds ago   Up 39 seconds   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp            nginx-routing-PFCYHt-1

curl localhost:8080

Test the running container, run the following command:

curl localhost:8080 -H "Host: dnspb7p6y.localhost"
Welcome to nginx!

Congrats! You’ve successfully deploy, with the score-compose implementation, a simple Nginx containerized workload exposed via a DNS. You provisioned them through Docker, without writing the Docker Compose file by yourself.

3. score-k8s

To begin, follow the installation instructions to install the latest version of score-k8s.

init

Initialize your current score-k8s workspace, run the following command in your terminal:

score-k8s init --no-sample \
    --patch-templates https://raw.githubusercontent.com/score-spec/community-patchers/refs/heads/main/score-k8s/unprivileged.tpl

The init command will create the .score-k8s directory with the default resource provisioners available.

You can see the resource provisioners available by running this command:

score-k8s provisioners list

The Score file example illustrated uses three resource types: dns, route and volume.

+---------------+-------+------------------+--------------------------------+---------------------------------+
|     TYPE      | CLASS |      PARAMS      |            OUTPUTS             |          DESCRIPTION            |
+---------------+-------+------------------+--------------------------------+---------------------------------+
| dns           | (any) |                  | host                           | Outputs a *.localhost domain    |
|               |       |                  |                                | as the hostname                 |
+---------------+-------+------------------+--------------------------------+---------------------------------+
| route         | (any) | host, path, port |                                | Provisions an Ingress route on  |
|               |       |                  |                                | a shared Nginx instance         |
+---------------+-------+------------------+--------------------------------+---------------------------------+
| volume        | (any) |                  | source                         | Creates a persistent volume     |
|               |       |                  |                                | that can be mounted on a        |
|               |       |                  |                                | workload                        |
+---------------+-------+------------------+--------------------------------+---------------------------------+

By using the --patch-templates (in this case: unprivileged.tpl) we are also making sure that the generated workload will run as unprivileged.

generate

Convert the score.yaml file into a runnable manifests.yaml, run the following command in your terminal:

score-k8s generate score.yaml --image nginxinc/nginx-unprivileged:alpine-slim

The generate command will add the input score.yaml workload with a particular container image to the .score-k8s/state.yaml state file and generate the output manifests.yaml.

See the generated manifests.yaml by running this command:

cat manifests.yaml

If you make any modifications to the score.yaml file, run score-k8s generate score.yaml to regenerate the output manifests.yaml.

resources

Get the information of the resources dependencies of the workload, run the following command:

score-k8s resources list
+---------------------------+---------+
|            UID            | OUTPUTS |
+---------------------------+---------+
| dns.default#nginx.dns     | host    |
+---------------------------+---------+
| volume.default#nginx.tmp  | source  |
+---------------------------+---------+
| route.default#nginx.route |         |
+---------------------------+---------+

At this stage, we can already see the value of the dns resource generated:

score-k8s resources get-outputs dns.default#nginx.dns --format '{{ .host }}'
dnsev272w.localhost

kubectl apply

Here you will need to have access to a Kubernetes cluster to execute the following commands. You can follow these instructions if you want to set up a Kind cluster.

Run kubectl apply to execute the generated manifests.yaml file:

kubectl apply -f manifests.yaml
httproute.gateway.networking.k8s.io/route-nginx-0148edc5 created
service/nginx created
deployment.apps/nginx created

kubectl get all

See the running containers:

kubectl get all
NAME                         READY   STATUS    RESTARTS   AGE
pod/nginx-6947586bd6-82lvj   1/1     Running   0          16s

NAME                 TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/nginx        ClusterIP   10.96.38.94   <none>        8080/TCP   17s

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx   1/1     1            1           17s

NAME                               DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-6947586bd6   1         1         1       17s

curl localhost

Test the running container, run the following command:

curl localhost -H "Host: dnsev272w.localhost"
Welcome to nginx!

Congrats! You’ve successfully deploy, with the score-k8s implementation, a simple Nginx containerized workload exposed via a DNS. You provisioned them through kubectl, without writing the Kubernetes manifests file by yourself.

Next steps

  • Explore more examples: Check out more examples to dive into further use cases and experiment with different configurations.
  • Join the Score community: Connect with fellow Score developers on our CNCF Slack channel or start find your way to contribute to Score.