Creating your first terraform repository

Ned Bellavance
9 min read


Terraform is an infrastructure as code tool. The key term here being code. And where do you store your code? In a version control system like GitHub! I recently published a video with the awesome April Edwards on creating your first Terraform repository.

This blog post is a companion to that video. Let’s dive in!

Why Use Version Control?

If you’re seasoned developer using version control, then I don’t need to convince you of its benefits. But if you’re like me and coming from a sysadmin background, then you might not be as familiar with version control software or its benefits. Here are some reasons why you should use version control:

  • Backup - Your code is stored in a remote location
  • Versioning - You can see the history of your code and roll back to previous versions if needed
  • Collaboration - Multiple people can work on the same codebase
  • Code Review - Others can review and approve changes before they’re merged and applied
  • Automation - You can automate testing and deployment of your code on check in

With all that in mind, how do you get started with version control for your Terraform code? First we need to go over some terminology and tools.


The most popular source control software in the world is git. It’s leveraged by GitHub, GitLab, Bitbucket, and many other platforms. There are other source control systems like Subversion, but I don’t know anything about those, so we’ll focus on using git.

Git tracks your code through a repository. Your code is checked into a local repository managed by git. The repository tracks the various versions of code through commit IDs and branches. To add a file to git, you first stage the file, then commit it to the repository. The commit process creates a new commit ID and stores the file in the repository.

There’s a LOT more to it than just that. But for our purposes, the important thing is to understand the process of staging files and committing them to the repository. Just saving a file to the directory being used by git doesn’t mean it’s being tracked by git.

In addition to your local repository, you can also configure one or more remote repositories. This allows you to push your local code to a remote location, collaborate on the code with others, and pull down updates they have made.

While you’ll certainly want to commit and track your Terraform files, like or, there are some files that you don’t want to commit. For instance your local state file (if you’re using local state) or the .terraform directory that holds the provider plugins and modules used by your code. You can tell git to ignore these files by creating a .gitignore file in your repository.

The .gitignore file is a special file that tells git what files to exclude from commits. You can specify files by name, or use wildcards to exclude entire directories or file extensions.

Now that we have some background in version control and git, it’s time to create a repository!

Creating a Local Repository

If you’ve been using Terraform for a while now, there’s a good chance you have a bunch of directories with your code in them. Maybe you’ve been sharing the code by zipping it up and emailing it, or copying it to a shared drive. Now it’s time to try a better way by storing that code in a git repository. Let’s see how you can start by creating a local repository for your existing code.

In my example, we have a directory called taco-app that has the following file tree:

taco-app$ tree -a -L 1
├── .terraform
├── .terraform.lock.hcl
├── terraform.tfstate
├── terraform.tfvars

This is the code that we want to store in a repository. First we’ll run a git command to create the repository in the taco-app directory. In the same way that you initialize a Terraform configuration with terraform init, you start your git journey by running git init.

taco-app$ git init -b main
Initialized empty Git repository in .../taco-app/.git/

A new repository needs a default branch for git to track. We can use the -b flag to set the default branch name for our repository. In this case, we used main as the branch name.

The command created a hidden folder called .git, which will store information about the files being tracked, commit ids, and configuration of our git client for this repository.

taco-app$ tree -L 1 .git
├── HEAD
├── branches
├── config
├── description
├── hooks
├── info
├── objects
└── refs

Now we can add our files to the repository. Before we do that, I’m going to set up a .gitignore file to exclude the .terraform directory and the terraform.tfstate file. We don’t want to commit these files to the repository. You can find a good .gitignore file for Terraform on GitHub.

The command to stage our files is git add followed by the files we want to add. Rather than laboriously typing out all the files and directories, instead we can use the . to add all files and subfolders of the current working directory.

taco-app$ git add .

You won’t see any output from this command if everything has gone well. If we want to see what files have been staged, we can run git status:

taco-app$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   .gitignore
        new file:   .terraform.lock.hcl
        new file:
        new file:
        new file:
        new file:

With our files staged, now we can commit them to source control using the git commit command. This command requires that you provide a message about the commit. The message can be anything you like, but it should be descriptive of the changes being introduced by your commit.

taco-app$ git commit -m "First commit"
[main (root-commit) 827b4dd] First commit
 6 files changed, 58 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .terraform.lock.hcl
 create mode 100644
 create mode 100644
 create mode 100644
 create mode 100644

Our files are now part of the repository! But right now they’re only stored on our local machine. If we want to collaborate with others, or have a backup of our code, we need to push it to a remote repository.

Creating a Remote Repository on GitHub

To create a remote repository on GitHub, you’ll need to have an account. If you don’t have one, go to GitHub and sign up.

You can create a new repository using the web interface or the GitHub command line tool. Since we’re doing everything else with the command line, we might as well use it to create our remote repository too. If you don’t have the GitHub CLI tool installed, you can download it from the GitHub CLI website.

If you’ve never used the tool before, you’ll need to login into GitHub using the command gh auth login. The wizard will walk you through authenticating with GitHub and procuring a token for the CLI to use.

We can create the repository using the gh repo create command. Since we already have the local repository, we can let GitHub know that it should use our local repository by specifying the --source flag. We also want to make the repository public, so we’ll use the --public flag. Finally, we want to add a remote entry to our local repository, so we’ll use the --remote flag to specify the name of the remote repository entry.

taco-app$ gh repo create --public --source=. --remote=upstream
✓ Created repository ned1313/taco-app on GitHub
✓ Added remote

Awesome! We’ve successfully created a local repository for our Terraform code and a remote repository on GitHub. Now we need to push our local code to the remote repository.

Pushing Changes to the Remote Repository

Our local repository now knows about the remote repository on GitHub. We can view the details of the remote repository by running the git remote show command with the name of the remote entry:

taco-app$ git remote show upstream
* remote upstream
  Fetch URL:
  Push  URL:
  HEAD branch: (unknown)

See that HEAD branch: (unknown) line? That’s because we haven’t pushed our local code to the remote repository yet. We can do that using the git push command. The -u flag tells git to set the main branch on the remote repository called upstream to track our local main branch.

taco-app$ git push -u upstream main
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 16 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (9/9), 1.79 KiB | 37.00 KiB/s, done.
Total 9 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), done.
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'upstream'.

This also means that in the future, when we run git push, git will know to push to the remote branch we’ve set in the upstream entry.

Now if you check the info for the remote repository, you’ll see that the HEAD branch is set to main:

taco-app$ git remote show upstream
* remote upstream
  Fetch URL:
  Push  URL:
  HEAD branch: main
  Remote branch:
    main tracked
  Local branch configured for 'git pull':
    main merges with remote main
  Local ref configured for 'git push':
    main pushes to main (up to date)

Awesome! We’ve taken our Terraform code, put it in a local repository, and pushed the code up to GitHub. Now we can collaborate with others, have a backup of our code, and automate testing and deployment of our code on check in.

Next Steps

Using version control for your Terraform code is a great first step. But there’s a lot more you can do with version control. You can create branches to work on new features or bug fixes. You can create pull requests to review and approve changes before they’re merged and applied. You can even automate testing and deployment of your code on check in.

In a future blog post and video, I’ll dig into how you can use source control to publish your own Terraform modules!