Aliases and Version Constraints: Which Configuration Builds It?
In one sentence
An alias is attaching a label to a provider's second configuration, and a version constraint is deciding how far tomorrow morning's CI may pick up a new release. Both are devices that leave a "choice" in the code.
Why this was needed
A provider configuration holds connection information such as credentials, endpoints, and regions. A resource is created by choosing one of them. When there is only one configuration, there is nothing to choose, so this choice is invisible. The moment a second one appears, every resource must answer — which one are you created from?
If you do not answer this question, the tool uses the default configuration. It uses it silently. So an incident like "I meant to create it in the DR region but created it in the primary region again" passes without an error. Worse is what comes next. The state file records which configuration that resource was created with, so when you delete it later, it is deleted with the same configuration. The wrong pairing hardens as it is.
A version constraint is the same problem from a different direction. If you do not write a constraint, it works today and tomorrow CI hits an error you have never seen. That is because a new release came out. Conversely, if you nail it to one release, you must bump it by hand even when a security fix comes out. A constraint is a sentence that states where you stand between these two.
How it works
You need to distinguish three things.
provider "random" {} # 기본 설정 — 이름표 없음
provider "random" {
alias = "seeded" # 두 번째 설정 — 이름표 seeded
}
resource "random_pet" "a" {
provider = random.seeded # 리소스는 provider (단수)
}
module "site" {
providers = { # 모듈은 providers (복수, 맵)
random.east = random
random.west = random.seeded
}
}
The Korean comments in this code say, in order: the default configuration with no label; the second configuration with the label seeded; a resource uses provider (singular); and a module uses providers (plural, a map).
- A resource uses
provider, a module usesproviders. The names are similar and people often get them wrong, but the meanings are entirely different: one says "I use this configuration," and the other says "this name inside the module is that configuration outside." - A module does not create its own configuration. If you declare a
providerblock inside a module, that module decides its own credentials and can no longer be used twice. Instead, it declares only the slot withconfiguration_aliases, saying "I will accept a configuration of this name." If the calling side does not fill that slot,initblocks and points out exactly which name it could not receive. - The state remembers. Each resource in the state file has a
providerstring, and only those created with an alias end differently. No matter how much you fix the code, the pairing of what has already been created is as written in the state.
For version constraints, the number of positions creates the meaning. A tilde constraint allows only the rightmost position to go up. If you write two positions, it accepts the middle position going up as well, and if you write three, it accepts only the last position. In the lab you will see for yourself these two produce different results against the same mirror.
The core version constraint (required_version) is yet another layer. This is checked before even fetching plugins, so it blocks even if required_providers is entirely absent. When it blocks, the tool tells you the version you are currently using as it is.
What it looks like in the field
The most common incident is calling a module twice, copying the provider map, and failing to fix just one line. The syntax is right and init passes too. The second call ends up using the same configuration as the first, and what should have been split across two regions gets concentrated on one side. The only way to catch it in review is to make people look at the whole map on every module call.
The second is using it with no constraint and one day only CI breaks. A person's working directory already has a lock file, so the old release keeps being used, and only the clean CI workspace picks up the new release. It is the classic "it works on my machine." This is why you commit the lock file.
The third is narrowing the constraint too much and forgetting it. If settings nailed to one release are scattered across dozens of repositories, then when a security fix comes out, finding the places to bump becomes the first job. In practice, you build the order by loosening the constraint only in the dev repositories and bumping those first to try.
The fourth is using environment names as alias names. If you write provider "aws" { alias = "prod" }, it reads well, but which region and which account that configuration is still does not show. If you later move accounts, the name and the reality diverge, and nobody fixes a diverged name. An alias lasts longer if you name it for what it connects to.
To summarize, the two things this module covers are both about "leaving a choice in the code." If you write down for each resource which configuration to create it with, the state remembers that choice, and if you write down as a constraint how far a new release may be picked up, the lock file leaves the basis for that decision. If you write neither, the tool silently decides on your behalf, and that decision becomes visible only after an incident.
What to do in the next lab
In /root/tfa-alias you configure the random provider twice, choose for each resource, and pass that configuration to a module. Next you remove one line from the map and receive, as an error, what init says, ask for a version that is not in the mirror and get rejected, confirm that two tilde constraints allow different things, and then read values from the lock file and the state and organize them into a table.