Purpose of this file
Expose the specific values people and other tools need after Terraform runs — without requiring anyone to dig through the state file directly.
Terraform's state file contains far more detail than anyone actually needs to read by hand. Outputs are a deliberate, curated summary: exactly the handful of values someone setting up application deployment, or writing a script, would actually want.
The full file code sample
terraform/outputs.tfoutput "resource_group_name" {
value = azurerm_resource_group.metals.name
}
output "api_webapp_name" {
description = "Must match AZURE_WEBAPP_NAME in the GitHub Actions workflow."
value = azurerm_linux_web_app.api.name
}
output "api_webapp_url" {
value = "https://${azurerm_linux_web_app.api.default_hostname}"
}
output "ui_webapp_name" {
value = azurerm_linux_web_app.ui.name
}
output "ui_webapp_url" {
value = "https://${azurerm_linux_web_app.ui.default_hostname}"
}
output "tutorials_webapp_name" {
value = azurerm_linux_web_app.tutorials.name
}
output "tutorials_webapp_url" {
description = "The hosted tutorial site."
value = "https://${azurerm_linux_web_app.tutorials.default_hostname}"
}
output "container_registry_login_server" {
description = "Push metals-api/metals-ui here, e.g. with az_deploy.ps1/.sh, before apply pulls a new image_tag."
value = azurerm_container_registry.metals.login_server
}
output "database" {
description = "Connection details for the schema loader; password is supplied separately."
value = {
host = azurerm_postgresql_flexible_server.metals.fqdn
port = 5432
user = local.db_user
dbname = azurerm_postgresql_flexible_server_database.metals.name
sslmode = "require"
}
}
output "github_secrets" {
description = "Copy these IDs to GitHub repository secrets. These are identifiers, not passwords."
value = {
AZURE_CLIENT_ID = azurerm_user_assigned_identity.github.client_id
AZURE_TENANT_ID = azurerm_user_assigned_identity.github.tenant_id
AZURE_SUBSCRIPTION_ID = var.subscription_id
}
}
output "github_federated_subject" {
value = azurerm_federated_identity_credential.github.subject
}
Each output, explained walkthrough
The name of the resource group created in main.tf — useful for scripting anything that needs to target it, like the Azure CLI commands the GitHub Actions workflow runs directly.
Each web app's name. These must match the API_WEBAPP_NAME and UI_WEBAPP_NAME values at the top of .github/workflows/deploy-api.yml and deploy-ui.yml, since those workflows address the apps by name rather than looking them up through Terraform.
They were a single webapp_name output when the API was the only deployable app. The rename to api_* was necessary once a second app existed — and it's exactly the kind of change that quietly breaks any script still asking for the old name.
value = "https://${azurerm_linux_web_app.api.default_hostname}"
An output's value doesn't have to be a plain attribute — here it's a small expression, wrapping each app's auto-generated hostname in a full, clickable https:// URL. The UI's is the one to actually open in a browser; the API's is useful for curl and health checks.
The registry's hostname, like expeditorsdzierzonmetalsacr.azurecr.io. Terraform creates the registry but never puts anything in it, so this output is the handoff point: it tells you (or a script) exactly where metals-api and metals-ui need to be pushed before the web apps have anything to run.
An output's value can also be a whole object, not just a single string — here, a small map of connection details (host, port, user, dbname, sslmode). Notice the password is deliberately left out; this output is consumed by terraform/scripts/initialize_database.py, which loads the password separately from the same TF_VAR_db_password source Terraform itself uses, rather than round-tripping it through an output.
The three identifiers someone needs to paste into the application deployment workflow's GitHub environment secrets (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID), grouped into one output so a single command prints exactly what's needed. As the description notes, these are identifiers that describe which identity to use — not secrets that grant access on their own; the OIDC trust relationship from github_oidc.tf is what actually authorizes access.
Echoes back the exact OIDC subject string Azure was configured to trust, so it can be compared directly against a GitHub Actions error message if authentication ever fails — a quick way to spot a mismatch.
Reading outputs after apply how they get used
This is a quick preview — terraform output has its own full page covering preconditions, postconditions, and common errors. (dig deeper)
| Command | What it shows |
|---|---|
terraform output | All outputs, formatted for a person to read. |
terraform output -raw ui_webapp_url | One output's value only, with no extra formatting — useful in scripts. |
terraform output -json github_secrets | One structured output as JSON, so another program can parse it directly. |
Key terms for beginners
- Output
- A named value a Terraform configuration exposes after running, chosen deliberately by the file's author rather than requiring someone to read the raw state file.
- Structured output
- An output whose value is an object or map (multiple named fields) rather than a single string or number.