Frontend & Backend
score-compose
and score-k8s
8 minute read
Overview
In this example we will walk you through how you can deploy a containerized Frontend application talking to a Backend application, and this with both score-compose
and score-k8s
.
flowchart TD dns[DNS] --> frontend-workload(Frontend) dns[DNS] --> backend-workload(Backend) subgraph Workloads frontend-workload backend-workload end frontend-workload -.-> dns dns -.-> backend-workload backend-workload-->postgres[(PostgreSQL)]
Score files
We will describe each containerized application with its own Score file for each. The demo code can be found here.
Open your IDE and paste in the following score-frontend.yaml
file, which describes the containerized Nginx Frontend application exposed via a DNS that contact a Backend application on each request.
apiVersion: score.dev/v1b1
metadata:
name: frontend
containers:
frontend:
image: .
variables:
APP_CONFIG_app_baseUrl: ${resources.dns.url}
APP_CONFIG_backend_baseUrl: ${resources.dns.url}
livenessProbe:
httpGet:
path: /healthcheck
port: 8080
readinessProbe:
httpGet:
path: /healthcheck
port: 8080
service:
ports:
tcp:
port: 3000
targetPort: 8080
resources:
backend:
type: service
dns:
type: dns
id: dns
route:
type: route
params:
host: ${resources.dns.host}
path: /
port: 3000
The following score-backend.yaml
file describes the containerized the NodeJS Backend application talking to a PostgreSQL database:
apiVersion: score.dev/v1b1
metadata:
name: backend
containers:
backend:
image: .
command:
- "node"
args:
- packages/backend
- "--config"
- app-config.yaml
- "--config"
- app-config.production.yaml
variables:
POSTGRES_HOST: ${resources.pg.host}
POSTGRES_PASSWORD: ${resources.pg.password}
POSTGRES_PORT: ${resources.pg.port}
POSTGRES_USER: ${resources.pg.username}
APP_CONFIG_auth_providers_guest_dangerouslyAllowOutsideDevelopment: "true"
APP_CONFIG_backend_cors_origin: ${resources.dns.url}
APP_CONFIG_techRadar_url: https://github.com/mathieu-benoit/humanitec-ref-arch/blob/main/tech-radar.json
livenessProbe:
httpGet:
path: /.backstage/health/v1/liveness
port: 7007
readinessProbe:
httpGet:
path: /.backstage/health/v1/readiness
port: 7007
service:
ports:
tcp:
port: 7007
targetPort: 7007
resources:
pg:
type: postgres-instance
dns:
type: dns
id: dns
route:
type: route
params:
host: ${resources.dns.host}
path: /api
port: 7007
The id: dns
part under the dns
resources in both Score files allow to generate a shared dns
between both frontend
and backend
Workloads.
Deployment with score-compose
and score-k8s
From here, we will now see how to deploy these exact same Score files with either with score-compose
or with score-k8s
:
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/service/score-compose/10-service.provisioners.yaml \
--provisioners https://raw.githubusercontent.com/score-spec/community-provisioners/refs/heads/main/dns/score-compose/10-dns-with-url.provisioners.yaml
The init
command will create the .score-compose
directory with the default resource provisioners available. We are also importing two external files to support the dns
dependencies: dns
provisioner and service
dependencies: service
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: postgres-instance
, dns
and route
.
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| TYPE | CLASS | PARAMS | OUTPUTS | DESCRIPTION |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| dns | (any) | | host, url | Outputs a *.localhost domain as |
| | | | | the hostname and associated URL |
| | | | | in http on port 8080 |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| postgres-instance | (any) | | host, password, port, username | Provisions a dedicated |
| | | | | PostgreSQL instance |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| route | (any) | host, path, port | | Provisions an Ingress route on |
| | | | | a shared Nginx instance |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| service | (any) | | name | Outputs the name of the |
| | | | | Workload dependency if it |
| | | | | exists in the list of Workloads |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
generate
Convert the score-frontend.yaml
and score-backend.yaml
files into a deployable compose.yaml
, run the following command in your terminal:
score-compose generate score-backend.yaml \
--image ghcr.io/mathieu-benoit/backstage-backend:latest
score-compose generate score-frontend.yaml \
--image ghcr.io/mathieu-benoit/backstage-frontend:latest
The generate
command will add the input score-frontend.yaml
and score-backend.yaml
workloads 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-backend.yaml --build 'main={"context":".","tags":["backend:local"]}'
See the generated compose.yaml
by running this command:
cat compose.yaml
If you make any modifications to the score-frontend.yaml
or score-backend.yaml
files, 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#dns | host, url |
+--------------------------------------+--------------------------------+
| postgres-instance.default#backend.pg | host, password, port, username |
+--------------------------------------+--------------------------------+
| service.default#frontend.backend | name |
+--------------------------------------+--------------------------------+
| route.default#backend.route | |
+--------------------------------------+--------------------------------+
| route.default#frontend.route | |
+--------------------------------------+--------------------------------+
At this stage, we can already see the value of the dns
resource generated:
score-compose resources get-outputs dns.default#dns
{
"host": "dnsjdtv57.localhost",
"url": "http://dnsjdtv57.localhost:8080"
}
Same for the postgres-instance
resource:
score-compose resources get-outputs postgres-instance.default#backend.pg
{
"host": "pg-OuoTNo",
"password": "REDACTED",
"port": 5432,
"username": "REDACTED"
}
docker compose
Run docker compose up
to execute the generated compose.yaml
file:
docker compose up -d
[+] Running 5/5
✔ Container deploy-backstage-with-score-routing-5QD6z8-1 Running
✔ Container deploy-backstage-with-score-pg-glZvLw-1 Healthy
✔ Container deploy-backstage-with-score-wait-for-resources-1 Exited
✔ Container deploy-backstage-with-score-backend-backend-1 Running
✔ Container deploy-backstage-with-score-frontend-frontend-1 Running
docker ps
See the running containers:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1712a2002838 ghcr.io/mathieu-benoit/backstage-frontend:latest "/docker-entrypoint.…" 3 minutes ago Up 2 minutes 80/tcp deploy-backstage-with-score-frontend-frontend-1
6bf734ab9179 ghcr.io/mathieu-benoit/backstage-backend:latest "node packages/backe…" 3 minutes ago Up 2 minutes -backend-backend-1
9a126ed0456c mirror.gcr.io/nginx:1-alpine "/docker-entrypoint.…" 3 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp, [::]:8080->80/tcp deploy-backstage-with-score-routing-5QD6z8-1
5ec1732b6695 mirror.gcr.io/postgres:17-alpine "docker-entrypoint.s…" 3 minutes ago Up 3 minutes (healthy) 5432/tcp -pg-glZvLw-1
curl localhost:8080
Test the running container, run the following command:
curl localhost:8080 -H "Host: dnsjdtv57.localhost"
...
<title>Scaffolded Backstage App</title>
...
Congrats! You’ve successfully deploy, with the score-compose
implementation, a containerized Frontend application talking to a containerized Backend application exposed via a DNS. You provisioned them through Docker, without writing the Docker Compose file by yourself.
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/service/score-k8s/10-service.provisioners.yaml \
--provisioners https://raw.githubusercontent.com/score-spec/community-provisioners/refs/heads/main/dns/score-k8s/10-dns-with-url.provisioners.yaml
The init
command will create the .score-k8s
directory with the default resource provisioners available. We are also importing two external files to support the dns
dependencies: dns
provisioner and service
dependencies: service
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: postgres-instance
, dns
and route
.
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| TYPE | CLASS | PARAMS | OUTPUTS | DESCRIPTION |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| dns | (any) | | host, url | Outputs a *.localhost domain as |
| | | | | the hostname and associated URL |
| | | | | in http on port 80 |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| postgres-instance | (any) | | host, password, port, username | Provisions a dedicated |
| | | | | PostgreSQL instance |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| route | (any) | host, path, port | | Provisions an Ingress route on |
| | | | | a shared Nginx instance |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
| service | (any) | | name | Outputs the name of the |
| | | | | Workload dependency if it |
| | | | | exists in the list of Workloads |
+-------------------+-------+------------------+--------------------------------+---------------------------------+
generate
Convert the score-frontend.yaml
and score-backend.yaml
files into a deployable manifests.yaml
, run the following command in your terminal:
score-k8s generate score-backend.yaml \
--image ghcr.io/mathieu-benoit/backstage-backend:latest
score-k8s generate score-frontend.yaml \
--image ghcr.io/mathieu-benoit/backstage-frontend:latest
The generate
command will add the input score-frontend.yaml
and score-backend.yaml
workloads 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-frontend.yaml
or score-backend.yaml
files, 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#dns | host, url |
+--------------------------------------+--------------------------------+
| postgres-instance.default#backend.pg | host, password, port, username |
+--------------------------------------+--------------------------------+
| service.default#frontend.backend | name |
+--------------------------------------+--------------------------------+
| route.default#backend.route | |
+--------------------------------------+--------------------------------+
| route.default#frontend.route | |
+--------------------------------------+--------------------------------+
At this stage, we can already see the value of the dns
resource generated:
score-k8s resources get-outputs dns.default#dns
{
"host": "dnsnocrke.localhost",
"url": "http://dnsnocrke.localhost:80"
}
Same for the postgres-instance
resource:
score-k8s resources get-outputs postgres-instance.default#backend.pg
{
"host": "pg-backstage-d7058793",
"password": "REDACTED",
"port": 5432,
"username": "REDACTED"
}
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
secret/pg-backend-61f0e7c1 created
statefulset.apps/pg-backend-61f0e7c1 created
service/pg-backend-61f0e7c1 created
httproute.gateway.networking.k8s.io/route-backend-711726ce created
httproute.gateway.networking.k8s.io/route-frontend-61f9e1b8 created
service/backend created
deployment.apps/backend created
service/frontend created
deployment.apps/frontend created
kubectl get all
See the running containers:
kubectl get all,httproute
NAME READY STATUS RESTARTS AGE
pod/backend-57fc5664d-4hg7l 1/1 Running 0 22s
pod/frontend-db644cf86-n9cjd 1/1 Running 0 22s
pod/pg-backend-61f0e7c1-0 1/1 Running 0 60m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend ClusterIP 10.96.183.51 <none> 7007/TCP 60m
service/frontend ClusterIP 10.96.90.135 <none> 3000/TCP 60m
service/pg-backend-61f0e7c1 ClusterIP 10.96.45.108 <none> 5432/TCP 60m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/backend 1/1 1 1 60m
deployment.apps/frontend 1/1 1 1 60m
NAME DESIRED CURRENT READY AGE
replicaset.apps/backend-57fc5664d 1 1 1 22s
replicaset.apps/backend-64fc859b9d 0 0 0 60m
replicaset.apps/frontend-68ccb68884 0 0 0 60m
replicaset.apps/frontend-db644cf86 1 1 1 22s
NAME READY AGE
statefulset.apps/pg-backend-61f0e7c1 1/1 60m
NAME HOSTNAMES AGE
httproute.gateway.networking.k8s.io/route-backend-711726ce ["dnsxmfazk.localhost"] 60m
httproute.gateway.networking.k8s.io/route-frontend-61f9e1b8 ["dnsxmfazk.localhost"] 60m
curl localhost
Test the running container, run the following command:
curl localhost -H "Host: dnsnocrke.localhost"
...
<title>Scaffolded Backstage App</title>
...
Congrats! You’ve successfully deploy, with the score-k8s
implementation, a containerized Frontend application talking to a containerized Backend application exposed via a DNS. You provisioned them through kubectl
, without writing the Kubernetes manifests file by yourself.
Next steps
- Deep dive with the associated step-by-step guide: Go through the associated step-by-step guide to understand the different concepts with both: Backstage and Score.
- 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.