Dapr
score-compose
and score-k8s
7 minute read
Overview
In this example we will walk you through how you can deploy a NodeJS containerized application using a Dapr StateStore (Redis), and this with both score-compose
and score-k8s
.
flowchart TD dns[DNS] --> nodejs-workload(NodeJS) subgraph Workloads nodejs-workload end nodejs-workload-->dapr-statestore[Dapr StateStore] dapr-statestore-->redis[(Redis)]
1. score.yaml
Open your IDE and paste in the following score.yaml
file, which describes a simple web server exposed via a DNS that queries a Dapr StateStore (Redis) on each request. The demo code can be found here.
apiVersion: score.dev/v1b1
metadata:
name: nodeapp
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "nodeapp"
dapr.io/app-port: "3000"
containers:
nodeapp:
image: .
variables:
APP_PORT: "3000"
STATE_STORE_NAME: "${resources.state-store.name}"
service:
ports:
tcp:
port: 3000
targetPort: 3000
resources:
state-store:
type: dapr-state-store
dns:
type: dns
route:
type: route
params:
host: ${resources.dns.host}
path: /
port: 3000
From here, you can deploy this exact same Score file:
- Either with
score-compose
- Or with
score-k8s
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 \
--provisioners https://raw.githubusercontent.com/score-spec/community-provisioners/refs/heads/main/dapr-state-store/score-compose/10-redis-dapr-state-store.provisioners.yaml \
--patch-templates https://raw.githubusercontent.com/score-spec/community-patchers/refs/heads/main/score-compose/dapr.tpl
The init
command will create the .score-compose
directory with the default resource provisioners available. We are also importing one external file to seamlessly generate a Dapr StateStore Component
pointing to a Redis database: dapr-state-store
provisioner.
You can see the resource provisioners available by running this command:
score-compose provisioners list
The Score file example illustrated uses three resource types: dapr-state-store
, dns
and route
.
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| TYPE | CLASS | PARAMS | OUTPUTS | DESCRIPTION |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| dapr-state-store | (any) | | name | Generates a Dapr StateStore |
| | | | | Component pointing to a Redis |
| | | | | Service. |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| dns | (any) | | host | Outputs a *.localhost domain |
| | | | | as the hostname |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| route | (any) | host, path, port | | Provisions an Ingress route on |
| | | | | a shared Nginx instance |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
By using the --patch-templates
(in this case: dapr.tpl
) we are seamlessly generating the Dapr scheduler
and placement
containers in addition to a Dapr Sidecar
container for any Workload.
generate
Convert the score.yaml
file into a runnable compose.yaml
, run the following command in your terminal:
score-compose generate score.yaml --image ghcr.io/dapr/samples/hello-k8s-node:latest
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":["hello-dapr-node: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 |
+----------------------------------------------+---------+
| dapr-state-store.default#nodeapp.state-store | name |
+----------------------------------------------+---------+
| dns.default#nodeapp.dns | host |
+----------------------------------------------+---------+
| route.default#nodeapp.route | |
+----------------------------------------------+---------+
At this stage, we can already see the value of the dns
resource generated:
score-compose resources get-outputs dns.default#nodeapp.dns --format '{{ .host }}'
dnsbcsqnd.localhost
Same for the dapr-state-store
resource:
score-compose resources get-outputs dapr-state-store.default#nodeapp.state-store
{
"name": "redis-PANgkO"
}
docker compose
Run docker compose up
to execute the generated compose.yaml
file:
docker compose up -d
[+] Running 7/7
✔ Container dapr-redis-PANgkO-1 Started
✔ Container dapr-placement-1 Started
✔ Container dapr-scheduler-1 Started
✔ Container dapr-routing-aPCJB9-1 Started
✔ Container dapr-wait-for-resources-1 Exited
✔ Container dapr-nodeapp-nodeapp-1 Started
✔ Container dapr-nodeapp-nodeapp-sidecar-1 Started
docker ps
See the running containers:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3c7dfd658b1a mirror.gcr.io/redis:7-alpine "redis-server /usr/l…" About a minute ago Up About a minute 6379/tcp dapr-redis-PANgkO-1
85c836ae19d2 ghcr.io/dapr/daprd:latest "./daprd --app-id=no…" 8 hours ago Up 59 seconds dapr-nodeapp-nodeapp-sidecar-1
1f7affe5d910 ghcr.io/dapr/samples/hello-k8s-node:latest "docker-entrypoint.s…" 8 hours ago Up About a minute 3000/tcp dapr-nodeapp-nodeapp-1
e7e1ae181a28 mirror.gcr.io/nginx:1-alpine "/docker-entrypoint.…" 8 hours ago Up About a minute 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp dapr-routing-aPCJB9-1
f2983452962a ghcr.io/dapr/scheduler:latest "./scheduler --port …" 8 hours ago Up About a minute 0.0.0.0:50007->50007/tcp, :::50007->50007/tcp dapr-scheduler-1
913af9a5c8fc ghcr.io/dapr/placement:latest "./placement --port …" 8 hours ago Up About a minute 0.0.0.0:50006->50006/tcp, :::50006->50006/tcp dapr-placement-1
curl localhost:8080
Test the running container, run the following command:
curl localhost:8080 -H "Host: dnsbcsqnd.localhost"
This will get the expected error showing that the container is successfully running:
Cannot GET /
You can check the logs of the running container:
Node App listening on port 3000!
Congrats! You’ve successfully deploy, with the score-compose
implementation, a sample NodeJS containerized workload talking to a Dapr StateStore (Redis) and 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 \
--provisioners https://raw.githubusercontent.com/score-spec/community-provisioners/refs/heads/main/dapr-state-store/score-k8s/10-redis-dapr-state-store.provisioners.yaml
The init
command will create the .score-k8s
directory with the default resource provisioners available. We are also importing one external file to generate a Dapr StateStore Component
pointing to a Redis database: dapr-state-store
provisioner.
You can see the resource provisioners available by running this command:
score-k8s provisioners list
The Score file example illustrated uses three resource types: dapr-state-store
, dns
and route
.
+------------------+-------+------------------+--------------------------------+---------------------------------+
| TYPE | CLASS | PARAMS | OUTPUTS | DESCRIPTION |
+------------------+-------+------------------+--------------------------------+---------------------------------+
| dapr-state-store | (any) | | name | Generates a Dapr StateStore |
| | | | | Component pointing to a Redis |
| | | | | StatefulSet. |
+------------------+-------+------------------+--------------------------------+---------------------------------+
| dns | (any) | | host | Outputs a *.localhost domain |
| | | | | as the hostname |
+------------------+-------+------------------+--------------------------------+---------------------------------+
| route | (any) | host, path, port | | Provisions an Ingress route on |
| | | | | a shared Nginx instance |
+------------------+-------+------------------+--------------------------------+---------------------------------+
generate
Convert the score.yaml
file into a runnable manifests.yaml
, run the following command in your terminal:
score-k8s generate score.yaml --image ghcr.io/dapr/samples/hello-k8s-node:latest
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 |
+----------------------------------------------+---------+
| dapr-state-store.default#nodeapp.state-store | name |
+----------------------------------------------+---------+
| dns.default#nodeapp.dns | host |
+----------------------------------------------+---------+
| route.default#nodeapp.route | |
+----------------------------------------------+---------+
At this stage, we can already see the value of the dns
resource generated:
score-k8s resources get-outputs dns.default#nodeapp.dns --format '{{ .host }}'
dnsutripw.localhost
Same for the dapr-state-store
resource:
score-k8s resources get-outputs dapr-state-store.default#nodeapp.state-store
{
"name": "redis-nodeapp-58f89990"
}
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. Your Kubernetes cluster should also have Dapr installed in it.
Run kubectl apply
to execute the generated manifests.yaml
file:
kubectl apply -f manifests.yaml
secret/redis-nodeapp-58f89990 created
statefulset.apps/redis-nodeapp-58f89990 created
service/redis-nodeapp-58f89990 created
component.dapr.io/redis-nodeapp-58f89990 created
httproute.gateway.networking.k8s.io/route-nodeapp-3f3a9362 created
service/nodeapp created
deployment.apps/nodeapp created
kubectl get all
See the running containers:
kubectl get all
NAME READY STATUS RESTARTS AGE
pod/nodeapp-859d5458f6-75wjj 1/1 Running 0 3m43s
pod/redis-nodeapp-58f89990-0 1/1 Running 0 3m43s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nodeapp ClusterIP 10.96.138.145 <none> 3000/TCP 3m43s
service/nodeapp-dapr ClusterIP None <none> 80/TCP,50001/TCP,50002/TCP,9090/TCP 51s
service/redis-nodeapp-58f89990 ClusterIP 10.96.71.72 <none> 6379/TCP 3m43s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nodeapp 1/1 1 1 3m43s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nodeapp-859d5458f6 1 1 1 3m43s
NAME READY AGE
statefulset.apps/redis-nodeapp-58f89990 1/1 3m43s
curl localhost
Test the running container, run the following command:
curl localhost -H "Host: dnsutripw.localhost"
This will get the expected error showing that the container is successfully running:
Cannot GET /
You can check the logs of the running container:
Node App listening on port 3000!
Congrats! You’ve successfully deploy, with the score-k8s
implementation, a sample NodeJS containerized workload talking to a Dapr StateStore (Redis) and 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.
- Watch the Dapr + Score session at KubeCon EU 2025: Mixing the Perfect Cocktail for an Enhanced Developer Experience (video and repository), showing more advanced use cases with Dapr PubSub and Dapr Workflow.
- Join the Score community: Connect with fellow Score developers on our CNCF Slack channel or start find your way to contribute to Score.