Configuration, secrets, and infrastructure as code
The container promise from Lesson 4 of this module has important fine print.
The same application image can run on a developer’s laptop, in a test environment, and in production.
But it should not behave identically in every environment.
In a development environment, the application should connect to an isolated development or test database that developers can safely modify or erase. In production, it should connect to the production database containing authoritative customer and order data.
The application code may be the same, and so may the container image. So where do the differences live? They live in configuration.
What is configuration?
ConfigurationConcept · lights on your mapconfigurationThe collection of settings that controls how software behaves in a particular environment: which database, which endpoints, which features, what limits. Code defines what the application can do; configuration determines how it operates here. is the collection of settings that controls how software behaves in a particular environment. Configuration might tell an application:
- Which database to connect to
- Which API endpoint to call
- Which region it is running in
- Which log level to use
- Whether a feature is enabled
- How long a timeout should be
- Which storage bucket contains files
- How many requests may be processed at once
The application contains the general logic, and configuration supplies the environment-specific choices.
A useful principle is: code defines what the application can do. Configuration determines how it should operate here.
Different environments
Engineering teams commonly operate several separate environments.
Development
The environment where engineers build and test changes; it may run on a laptop or on shared development infrastructure.
Testing
An environment used by automated tests or quality-assurance processes.
Its data should generally be controlled and safe to replace.
Staging
An environment designed to resemble production closely enough to test releases before customers receive them.
Production
The live environment serving real users and handling authoritative data.
The names and number of environments vary between organizations, but the central goal is separation. An engineer testing a feature should not accidentally send messages to real customers, charge real cards, or delete production orders.
Same image, different configuration
Suppose the coffee backend reads a setting called DATABASE_URL.
In a development environment, the value might be: DATABASE_URL=postgres://localhost/coffee_test
In production, the value might be: DATABASE_URL=postgres://prod-db.coffeeapp.internal/coffee
The application code remains the same:
import osdatabase_url = os.environ["DATABASE_URL"]database = connect(database_url)The code does not permanently contain the address of either database.
Instead, it asks: which database address did this environment provide?
This is what allows one container image to operate in several controlled environments.
The simplified relationship is:
- Same application image + development configuration = development application
- Same application image + production configuration = production application
What is an environment variable?
The DATABASE_URL setting you just watched the code read has a name. It is an environment variableConcept · lights on your mapenvironment variableA named value supplied to a process when it runs (DATABASE_URL, LOG_LEVEL). Keeps environment-specific values outside source code and images, changeable without rebuilding. A delivery mechanism, not automatically a security system., a named value supplied to a process when it runs.
Whoever launches the process can hand it a set of these values. In development, that might be an engineer typing in a terminal. In production, it might be the container platform or the deployment system. The operating system keeps the values attached to that particular process, and the program can ask for any of them by name while it runs. The os.environ line in the previous section is exactly that question being asked.
The name fits this lesson’s theme. Each environment supplies its own values, so the same code behaves appropriately wherever it runs.
Teams use environment variables for values such as DATABASE_URL, LOG_LEVEL, PAYMENT_API_URL, and ENABLE_NEW_CHECKOUT.
Environment variables are popular because they keep environment-specific values outside the source code, work across many languages and platforms, and are easy to supply to containers. They can also be changed without rebuilding the application image, which suits automated deployment systems.
Other configuration methods
Environment variables are not the only way to deliver settings.
A configuration file collects settings in a document the application reads when it starts. Each environment gets its own file, or the deployment system writes the correct values into it. For example, a configuration file might contain:
database_host: prod-db.coffeeapp.internallog_level: inforequest_timeout_seconds: 10A command-line argument supplies a setting typed as part of the command that starts the program. Suppose an engineer is investigating a problem and wants one run with more detailed logging than the file’s info level. They might type: coffee-api --log-level=debug. That run records everything, and the next run returns to the file’s normal setting.
A configuration service stores settings in one central place. Applications request values from it while they run, so a team can change a setting without restarting anything.
The correct method depends on the application, the platform, and the sensitivity of the value.
The broader principle is more important than the delivery mechanism: environment-specific settings should be separated from the application’s permanent code and image.
What should be configuration?
Not every product decision should become a configurable setting.
Configuration is especially useful for values that legitimately differ between environments or deployments.
Examples include resource addresses, timeout lengths, logging detail, regional settings, operational limits, feature switches, and integration endpoints.
Too much configuration can make a system difficult to understand. If every behavior can change through hidden settings, engineers may not know which version of the product is actually running.
Good configuration should be:
- Clearly named
- Documented
- Validated
- Versioned where appropriate
- Given safe defaults when possible
- Observable in each environment
What is a feature flag?
A feature flag, sometimes called a feature toggle, is a configuration setting that enables or disables a product behavior.
For example: ENABLE_NEW_CHECKOUT=true. The application might read:
new_checkout_enabled = os.environ["ENABLE_NEW_CHECKOUT"] == "true"if new_checkout_enabled:show_new_checkout()else:show_existing_checkout()Feature flags allow teams to deploy code without immediately enabling it for every user. A team can test a feature with employees first, release it gradually, compare product variants, or disable a broken feature quickly. The deeper shift is that deployment and product launch become separate events.
Feature flags are powerful, but old flags should eventually be removed. Otherwise, the application accumulates many possible behavior combinations that become difficult to test.
What is a secret?
Some configuration values are sensitive enough to require special protection, and these values are called secretsConcept · lights on your mapsecretSensitive configuration that grants power: passwords, API keys, encryption and signing keys, credentials. Never permanently embedded in source code or container images; code gets copied, and version history remembers. All secrets are configuration; not all configuration is secret..
Examples include:
- Database passwords
- API keys
- Encryption keys
- Private certificates
- Token-signing keys
- OAuth client secrets
- Webhook-signing secrets
- Administrative credentials
A database address is ordinary configuration, but the password required to access that database is a secret.
A useful distinction is: all secrets are configuration, but not all configuration is secret.
Why secrets should not be hard-coded
A secret should generally not be written directly into source code: payment_api_key = "sk_live_…"
Source code travels. It is copied, reviewed, uploaded to shared code storage, included in backups, downloaded onto laptops, and sent to automated build systems. It is also preserved in version history, the running record of past changes that Module 11 explores.
Even if an engineer deletes the secret from the latest version, an older saved version may still contain it.
This is why that history matters. Deleting a visible line does not necessarily erase every previous copy.
The safer principle is: code should refer to a secret. It should not permanently contain the secret.
Secrets should not be stored in container images
Secrets should also generally not be built into a container image. For example, this is dangerous:
Container image├── application code├── libraries├── database password└── payment API keyAnyone who can retrieve or inspect the image may be able to recover those credentials, and container images may also be copied between registries and environments, increasing the number of places where the secret exists.
A safer design is:
- Container image → code and stable dependencies
- Runtime environment → supplies authorized secrets when needed
The same image can then run in development and production while receiving different credentials.
Environment variables are not automatically secure
Secrets are sometimes supplied through environment variables: DATABASE_PASSWORD=…
This keeps them out of the code and image, which is useful.
But an environment variable is only a delivery mechanism. It is not automatically a security system.
Depending on the environment, secrets in variables might be exposed through debugging tools, process inspection, logs, error messages, deployment configuration, administrative interfaces, or other software running with sufficient access.
Teams must still control who can set, read, and inspect those values.
The better mental model is: a secret can be delivered as an environment variable, but it should be stored and supplied through a protected process.
What is a secrets manager?
A secrets managerConcept · lights on your mapsecrets managerA service that stores sensitive values encrypted, checks the requesting identity’s permission, logs access, and supports rotation and revocation. The application requests the secret at runtime instead of carrying it. AWS’s is literally named Secrets Manager. is a service designed to store and control access to sensitive configuration values.
Instead of placing a database password in code, the company stores it in the secrets manager, and when the application starts (or when it needs the credential), it requests the secret using its authorized machine identity. The flow might look like:
Coffee backend starts→ proves its service identity→ requests database credential→ secrets manager checks permission→ authorized secret is returned→ backend connects to databaseThe application receives the secret without the developer permanently embedding it in the source code or image.
What a secrets manager provides
A secrets manager may support:
- Encryption of stored values
- Access-control policies
- Audit logs
- Versioning
- Automatic or assisted rotation
- Expiration
- Temporary credentials
- Controlled retrieval through APIs
- Integration with cloud identities
On AWS, one service designed for this purpose is named AWS Secrets Manager. Other cloud providers and independent companies offer similar systems.
Secret access still requires identity
A secrets manager cannot give every application every secret. The application must have an identity and permission to retrieve the specific values it needs.
For example:
- Coffee API → may read production database credential
- Photo-processing function → may read image-storage credential
- Analytics job → may not read payment credential
This is Module 6’s authentication and authorization model applied to infrastructure. The application proves who or what it is, and the secrets manager decides what that identity may retrieve.
The principle of least privilege applies: each workload should receive only the secrets necessary for its role.
Secret rotation
A secret should not necessarily remain unchanged forever.
Secret rotation means replacing an old credential with a new one. Rotation may be needed because a credential leaked, an employee changed roles, a policy requires periodic replacement, or a provider issued a new key. Sometimes the company simply wants to reduce long-term exposure.
A rotation process may need to:
- Create a new credential.
- Update the applications that use it.
- Confirm that the new credential works.
- Disable the old credential.
- Monitor for failed access.
- Remove the old value safely.
Managed systems can automate parts of this process, but rotation still requires applications and dependencies to handle credential changes correctly.
What happens when a secret leaks?
If a secret is exposed, deleting the line containing it is not enough. The team should assume that someone may already possess the value.
The usual response includes:
- Revoke or disable the credential
- Create a replacement
- Update affected systems
- Search for unauthorized use
- Review logs
- Determine where it was exposed
- Remove remaining copies
- Improve the process that allowed the leak
A leaked password remains dangerous until the system no longer accepts it.
This is another reason rotation and revocation capabilities matter.
Configuration drift
Now zoom out from application settings to the infrastructure itself.
Suppose an engineer manually creates a production environment by clicking through a cloud console.
They create four instances behind a load balancer across two availability zones, plus a database, a storage bucket, network rules, autoscaling policies, and permissions.
A month later, another engineer makes several manual changes, and six months later, no one knows exactly why production looks the way it does.
The documented design and the real environment have gradually diverged. This is called configuration drift.
Configuration drift makes systems difficult to reproduce, review, audit, recover, test, understand, and change safely.
The cloud resources are configured, but the reasoning and exact steps may exist only in someone’s memory.
What is infrastructure as code?
Infrastructure as codeConcept · lights on your mapinfrastructure as codeDefining infrastructure (networks, instances, databases, permissions, scaling rules) in machine-readable files a tool applies; Terraform is the name to know. Reviewable, versioned, repeatable: the structure can be rebuilt from its description. It rebuilds structure, not data., commonly shortened to IaC, means defining infrastructure through machine-readable files, not primarily through manual setup.
The definition might name the region, the network to create, and how many instances should run. It might state which container image to deploy, which database and storage buckets should exist, which permissions should be granted, and how autoscaling and the load balancer should behave.
A tool reads the definition and creates or updates the cloud resources.
A simplified infrastructure file might express:
Create a production network.Run backend instances in two availability zones.Maintain between 2 and 40 instances.Create a private managed database.Create an object-storage bucket.Allow the backend to access the database and bucket.The actual syntax depends on the tool. The important shift is: infrastructure becomes a defined, reviewable software artifact rather than a collection of undocumented clicks.
Declarative infrastructure
Many infrastructure-as-code tools use a declarative model.
The team describes the desired state: there should be four backend instances.
The tool compares that desired state with reality. If only three instances exist, it creates one, and if five exist, it may remove one.
This resembles the desired-state model you learned with Kubernetes. The team declares what should be true, and the system determines which actions are needed to make reality match.
Imperative infrastructure
Infrastructure can also be managed through imperative instructions.
An imperative script might say:
- Create network.
- Create instance 1.
- Create instance 2.
- Attach instances to load balancer.
This describes the steps to perform, not only the final desired state. Both styles can be useful.
A useful distinction is:
- Declarative: Describe the result you want.
- Imperative: Describe the steps used to produce it.
Modern infrastructure systems often combine both ideas.
What is Terraform?
Terraform is a widely used infrastructure-as-code tool originally created by HashiCorp.
Terraform lets teams describe infrastructure using configuration files.
A simplified Terraform-style definition might resemble:
resource "aws_s3_bucket" "photos" {bucket = "coffee-app-photos"}This says that an S3 bucket named coffee-app-photos should exist.
A larger Terraform project might define networks, EC2 instances, databases, storage, permissions, DNS records, load balancers, and autoscaling systems.
When engineers say “our infrastructure is in Terraform,” they generally mean that Terraform configuration files describe and manage important cloud resources, though Terraform is one major tool, not the only one.
Other approaches include AWS CloudFormation, the AWS Cloud Development Kit, Pulumi, Azure Bicep, configuration and orchestration tools, and custom automation.
Infrastructure files belong in version control
Infrastructure code can be stored in a version-control system (Module 11’s subject) alongside or near application code.
This allows teams to review proposed changes, see who changed what, track history, and revert problems. It also lets them run automated checks, reuse definitions, compare environments, and require approval before production changes.
For example, an engineer might propose a one-line change: increase maximum backend instances from 20 to 40.
Another engineer can review the exact change before it is applied, making the change a controlled process, not an undocumented console adjustment.
Infrastructure as code supports repeatability
Suppose the company needs a staging environment resembling production. Instead of manually rebuilding every resource, it can reuse the infrastructure definition with different configuration. For example:
Production:region = us-east-1minimum_instances = 2database_size = largeStaging:region = us-east-1minimum_instances = 1database_size = smallThe structure remains consistent while environment-specific values differ.
IaC can also support disaster recovery. If infrastructure in one region is lost, the definitions can help recreate networks, compute resources, permissions, and services elsewhere.
However, the infrastructure files do not automatically contain the lost customer data.
Infrastructure is not data
This distinction is important. Infrastructure as code may recreate the database service, the storage bucket, the network, the compute instances, the load balancers, and the permissions. It does not automatically restore what was inside them. Customer records, uploaded photos, transaction history, and stored secrets return only through backups, replication, restore procedures, data migration, and secrets-management systems.
A useful distinction is: infrastructure as code rebuilds the structure. Backups and replication recover the data.
A disaster-recovery plan may need both.
Infrastructure code should not contain secrets
Infrastructure files may need to refer to credentials or protected settings.
But secret values should generally not be written directly into the files: database_password = "actual-password"
If the files are stored in version control, the secret may be preserved and shared broadly.
Instead, the infrastructure code should refer to a secrets manager or another protected source. For example:
- Create the database.
- Create a secret entry for its credential.
- Allow the backend identity to retrieve that secret.
Infrastructure code defines how access should work without exposing the sensitive value itself.
Infrastructure changes can be dangerous
Infrastructure as code improves control, but it does not make every change safe.
A small text edit might delete a database, replace a network, remove public access, or grant broad permissions. It might terminate production instances, move resources, or significantly increase costs.
Teams therefore treat infrastructure changes seriously.
A mature process may include:
- Code review
- Automated validation
- Security checks
- Cost estimates
- Change previews
- Staging tests
- Approval requirements
- Backups
- Rollback or recovery plans
Infrastructure code is powerful precisely because it can change real systems.
What is a plan?
Many IaC tools can preview proposed changes before applying them. Terraform calls this preview a plan. A plan might say:
Create: 2 backend instancesUpdate: autoscaling maximum from 20 to 40Destroy: 0 resourcesThe team can inspect the plan before allowing Terraform to make the changes, which does not guarantee safety but provides visibility into the intended effect.
A useful workflow is: edit infrastructure code → review code → generate plan → review expected changes → apply → monitor result
Infrastructure as code and documentation
Infrastructure code is a form of executable documentation.
Instead of a diagram merely claiming “the application runs across two availability zones,” the infrastructure files can define the actual resources that implement it.
However, code does not eliminate the need for human documentation.
Infrastructure files may show what exists without explaining why the architecture was chosen, which risks it addresses, which business requirements apply, how to respond during a failure, or who owns each component.
Good systems use code, diagrams, operational instructions, and decision records together.
Seeing the full deployment model
Imagine that the coffee company deploys the same backend image into three environments.
Development
- Container image:
coffee-api:2.0 - Configuration: test database, debug logging, test payment provider
Staging
- Container image:
coffee-api:2.0 - Configuration: staging database, production-like logging, payment-provider test environment
Production
- Container image:
coffee-api:2.0 - Configuration: production database, normal logging, live payment provider
The image stays the same while configuration changes its environment-specific behavior, and production secrets are retrieved through a secrets manager.
Terraform defines the production network, compute, database, storage, load balancer, autoscaling rules, and permissions, and the deployment system starts the container with the correct configuration and authorized access. The complete model is:
Source code→ build one container image→ store image in registry→ infrastructure as code creates the environment→ configuration selects environment behavior→ secrets manager supplies protected credentials→ orchestrator starts the applicationEach concern has a separate home.
The mental model to remember
Configuration consists of settings that change how software behaves in a particular environment.
An environment variable is one way to supply a named configuration value to a running process.
Development, testing, staging, and production are separate environments with different purposes and data.
A feature flag enables or disables behavior without requiring an immediate new deployment.
A secret is sensitive configuration such as a password, API key, or encryption key.
Secrets should not be permanently embedded in source code or container images.
A secrets manager stores sensitive values, controls access, records retrieval, and may support rotation.
Configuration drift occurs when the real environment gradually diverges from its intended or documented setup.
Infrastructure as code, or IaC, defines infrastructure through machine-readable files.
Terraform is a widely used infrastructure-as-code tool.
A declarative system describes the desired state and works to make reality match it.
Infrastructure definitions can recreate resources, but restoring application data still requires backups, replication, and recovery procedures.
You should now understand how one application image can operate safely across several environments — and how teams make the surrounding infrastructure repeatable, reviewable, and recoverable.
A new engineer, moving fast, hard-codes the payments API key into the code “temporarily.” Rank the actual problem.
▼ answer the check to continue ▼