(It will be shorter afterwards since I don't know much, just used it frequently recently.)
1. add alias
in .bashrc, alias "terraform" to "tf" which is the file extension. It saves a lot of typing
2. input, output and local variables
when declare a variable as "var", it is an "input" variable. And when declare a variable as "output", literally it is that. They are _not_ for debug purpose. The input and output is relative to a module.
And then "locals" defines variable in tfvars format. It is even not taught in detail in documentation, but it is the most useful feature. The reason is that "var" cannot use interpolation, while "output" cannot be referenced in current module. The only way to define something useful is through "locals".
3. modules and root module
A folder can be loaded as a module. There is only one "root" module when terraform executes. And for each module, terraform will load all files in that folder. Recognized files are
— all .tf files
— all .auto.tfvars files
— I name the generated files ".auto.tf", which follows the first rule.
— special file terraform.tfvars. I avoid this special file at all -- put it in .gitignore just in case. When I have to use a tfvars file, for the generated files I name it ".auto.tfvars" which follows the second rule. For different environment I may also define "terraform.PROD.tfvars" which _won't_ get loaded until one uses the "-var-file" parameter.
The "root" module is singleton, but other folders can be imported multiple times, with different input variable values, get assigned a distinct module name. The output variable values for a module are calculated based on module input variable values, so different instances can have different values. So far it is not useful to me, though. The only important thing I can think of is to re-use the same scripts for different AWS regions?
4. shared modules
A project can have multiple "root" modules.. It is easier to update some resources together in one state file, while update other resources in a different one. Just the wrapper scripts have to go into each module to run "tf apply". These modules will have to share code ("common" modules).
5. module resource
do not define resource in imported modules.
6. module input variables
do not define default value for important module input variables. For a list, maybe define an empty list. If a module has to resolve resources based on a list, a default list can cause all users of this module to slow down.
if a "common" module grows too big, it will be so hard to use, because there will be so many mandatory parameters to assign values to. Better split modules to smaller ones.