Running companion containers with Score & Humanitec

Using pgAdmin as a companion to PostgreSQL as an example.
In a world where Internal Developer Platforms (IDPs) are all about making developers’ lives easier, tools like Score and Humanitec shine. They take away the mess of infrastructure provisioning and give you clean, repeatable interfaces. Spinning up a Postgres database? A couple lines in score.yaml
. Beautiful. But what happens when you want a companion container like pgAdmin—for dev environments only?
Let’s explore how to do exactly that.
The Setup: PostgreSQL + pgAdmin (Sometimes)
Score makes it dead simple to define a PostgreSQL database as a resource. Humanitec then takes that definition and provisions the DB in your environment. But what if we only want pgAdmin to show up in development, and not in staging or production?
That’s where Humanitec’s environment-specific dynamic configuration management comes into play. We’ll implement that behavior using Terraform code that configures the orchestrator to react accordingly.
Step-by-Step Tutorial
I have prepared a Github repo for you that contains all sources - so you can directly clone this and edit instead of C&P drama.
1. Define Your App with Score
Here’s a simplified version of your score.yaml
:
apiVersion: score.dev/v1b1
metadata:
name: pg-with-companion
containers:
app:
image: ghcr.io/astromechza/demo-app:latest
variables:
OVERRIDE_POSTGRES: "postgresql://${resources.db.username}:${resources.db.password}@${resources.db.host}:${resources.db.port}/${resources.db.database}"
resources:
db:
type: postgres
This asks for a PostgreSQL resource that Humanitec will provide. Also the database connection string is populated from the resource and allows the demo app to directly connect.
2. Configure the Orchestrator to Deploy pgAdmin Conditionally
Rather than configuring pgAdmin as a second container in your Score file , you configure the orchestrator to supply the companion automatically. It will be dynamically matched by the Humanitec orchestrator only for certain environments.
The example is fully self contained in main.tf
, all configuration code and setup goes in there. The Terraform configuration includes:
- A Humanitec application with development environment
- A resource definition that will run a PostgreSQL container
- and a pgAdmin companion container, including a service and more
- co-provision a route that points at this service
- A custom route definition that will only be used by pgAdmin co-provisioned routes and attach to the ingress of the workload with path based routing
- Matching rules that bind this workload to the PostgreSQL resource only if
context.env_type == development
This way, when deploying to dev, Humanitec includes pgAdmin automatically. In staging or production (or any other environment type for that matter), it omits it.
Let's highlight a few things that explain how it's coming together.
In the Score file, we also ask for a DNS resource - but we turn this into a shared resource by assigning an ID, to be able to use a reference to capture it more easily later.
resources:
dns:
type: dns
id: dns
The humanitec_resource_definition
- pg_with_attached_admin
is simply rendering more than just the PostgreSQL needed manifests - it also includes the manifests for pgAdmin. On top it uses co-provisioning to get the additional route into the graph. This not only provisions a route resource but one that has the class pgadmin.
provision = {
"route.pgadmin" = {
is_dependent = true,
match_dependents: false
}
}
The humanitec_resource_definition_criteria
for the route_for_pgadmin
now also specify the match on this class. That leads to the routes for pgAdmin using exactly this implementation, that differs from the standard route.
The humanitec_resource_definition
for route_for_pgadmin
captures relevant data from the graph using references and selectors, so that it not only points to the right path but also to the DNS of the original workload when providing the output. The output of all route resources is then picked up from the graph by the ingress resource using the same technique - a selector.
"pghost" = "$${resources['route.pgadmin>postgres'].outputs.host}"
"dns" = "$${resources['dns.default#shared.dns'].outputs.host}"
Notice the double $$ here, to preserve a singular dollar after the Terraform execution.
3. Apply the Terraform Configuration
Once your main.tf
and providers.tf
are set up with the appropriate dynamic module, apply the configuration:
terraform init
terraform apply
Or if you're a fan of make for platform agnostic baseline scripting, just run make init
.
This creates the setup and configuration in the orchestrator and prepares everything for your deployment.
4. Deploy and Verify
Use the Humanitec CLI to deploy:
humctl score deploy --app your-app-name --env development --file score.yaml
Check that pgAdmin is available in your dev environment.
If you want to test the negative case, then please create another environment in your app, using Terraform, the CLI or the UI as you wish. Then repeat for that new environment - here e.g. with staging and the CLI:
humctl create env staging --type staging --from development --app your-app-name
humctl score deploy --app your-app-name --env staging --file score.yaml
Take not how we're deploying the same score.yaml
here - no modifications. You should see pgAdmin missing in that case—exactly as intended.
Other Use Cases for This Pattern
This conditional deployment of companions is super useful beyond pgAdmin - or any companion admin UI:
- Web-App + Reverse Proxy
- Offload TLS termination, path-based routing, and static content serving to the proxy.
- App + Logging Agent / Metrics Exporter
- Collects and forwards logs to a centralized logging system (e.g., Elasticsearch, Loki). Configures the right logging endpoint depending on the environment, e.g. development/staging/prod.
- Exposes service metrics in Prometheus format without modifying the service container.
- App + OAuth Proxy
- I have a blog entry on that one - dive in to get more insights.
By modularizing and scoping these tools to specific environments, you avoid bloating your production workloads and keep security tight. You also can supply cross-cutting functionality to workloads so they don't need to implement them on their own - keeping your source code DRY. All being configured correctly for the stage that your deployment is targeting - without developers needing to spend a single thought on that.
Summary
- Score helps you declare infrastructure dependencies in the most simple way possible.
- Humanitec's orchestrator is configured (e.g., via Terraform) to match and provision resources dynamically.
- With environment-based rules, you can conditionally run companion tools like pgAdmin only where they’re needed. Your developers do not need to care about that, they are gifted with tools by the platform.
That’s clean, developer-friendly infrastructure automation—just the way I like it.
Member discussion