Cloud Portfolio GitHub Repository Structure: How to Organise Your Projects
How you organise a repository tells a hiring manager something before they read a single line of code. A flat directory with one enormous main.tf and no folder structure signals someone who has not worked on a real team. A well-organised project with logical folders, a clear README, and a separation of concerns signals someone who thinks about maintainability. This guide covers repo structure specifically for the inside of your cloud portfolio repositories.
General structure principles
Regardless of the project type, every cloud portfolio repository should have:
- A root-level
README.md— the first thing anyone reads - A
.gitignoreappropriate for the languages and tools used - No secrets, credentials, or
.tfstatefiles committed to the repository - A clear folder structure that groups related files — not everything at root level
- A
docs/ordiagrams/folder for architecture diagrams and supporting documentation (optional but impressive)
One rule that applies universally: if you have to ask where a file belongs, that question is a sign the structure needs more thought. Every file should have an obvious home.
Terraform repository structure
A Terraform project for a beginner to intermediate cloud engineer:
my-cloud-infra/
├── README.md
├── .gitignore # includes *.tfstate, *.tfstate.backup, .terraform/
├── modules/
│ ├── networking/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ ├── compute/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── outputs.tf
│ └── database/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── environments/
│ ├── dev/
│ │ ├── main.tf # calls modules with dev-specific values
│ │ ├── variables.tf
│ │ ├── outputs.tf
│ │ └── terraform.tfvars
│ └── prod/
│ ├── main.tf
│ ├── variables.tf
│ ├── outputs.tf
│ └── terraform.tfvars
├── .github/
│ └── workflows/
│ └── terraform.yml # CI pipeline for plan/apply
└── docs/
└── architecture.mdKey points in this structure:
- Modules are reusable — the same module code runs in dev and prod with different variable values
- The environment directories call modules, they do not duplicate infrastructure code
- The
.github/workflows/folder holds the CI pipeline alongside the infrastructure code - The
.gitignoreexplicitly excludes.terraform/directories and state files
Kubernetes repository structure
For a multi-service Kubernetes project:
my-k8s-app/
├── README.md
├── .gitignore
├── apps/
│ ├── frontend/
│ │ ├── Dockerfile
│ │ └── src/ # application source code
│ └── api/
│ ├── Dockerfile
│ └── src/
├── k8s/
│ ├── namespace.yaml
│ ├── frontend/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── hpa.yaml
│ ├── api/
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── configmap.yaml
│ └── ingress/
│ └── ingress.yaml
├── helm/ # if using Helm
│ └── my-app/
│ ├── Chart.yaml
│ ├── values.yaml
│ └── templates/
├── .github/
│ └── workflows/
│ ├── build.yml # build and push Docker images
│ └── deploy.yml # deploy to cluster
└── docs/
└── architecture.mdKey points:
- Application source code and Kubernetes manifests are in separate folders — mixing them creates confusion
- Kubernetes resources are grouped by service (
frontend/,api/), not by resource type — so you can navigate to all resources for a service without jumping around - The Helm chart is separate from the raw manifests — both are valid deployment methods; having both demonstrates versatility
- CI pipelines are in
.github/workflows/alongside the code they deploy
Serverless repository structure
For a serverless API project:
my-serverless-api/
├── README.md
├── .gitignore # includes .env, node_modules/, __pycache__/
├── src/
│ ├── handlers/
│ │ ├── create.py # or create.js, create.go
│ │ └── get.py
│ ├── models/
│ │ └── url.py
│ └── utils/
│ └── validation.py
├── tests/
│ ├── unit/
│ │ └── test_create.py
│ └── integration/
│ └── test_api.py
├── infrastructure/
│ ├── main.tf # or template.yaml for SAM
│ ├── variables.tf
│ ├── outputs.tf
│ └── iam.tf # IAM roles and policies in their own file
├── .github/
│ └── workflows/
│ └── ci.yml
└── docs/
└── api-reference.mdKey points:
- Application code is separated from infrastructure code — a common mistake is mixing Terraform and Python in the same directory
- Tests are separated into unit tests (fast, no network) and integration tests (slower, requiring deployed infrastructure)
- IAM resources are in their own file (
iam.tf) rather than embedded inmain.tf— IAM is complex enough to warrant its own file - No
.envfiles committed —.gitignorecovers this explicitly
README structure inside a repository
The README is the most important file in the repository. A strong README for a cloud portfolio project has these sections:
- Project title and one-line description — what is this and why does it exist?
- Architecture overview — a brief description or diagram of the components and how they connect
- Technologies used — the tools, services, and languages involved
- Architecture decisions — the most important section. Why did you choose this database? Why are the IAM permissions scoped this way? Why this deployment strategy?
- Prerequisites and deployment instructions — what does someone need to run this, and how?
- What you would change in production — honest acknowledgement of what the project lacks at portfolio scale
The architecture decisions section is what distinguishes a portfolio project from a tutorial. Do not skip it.
What always goes in .gitignore
For cloud portfolio projects, a comprehensive .gitignore should include:
# Terraform
*.tfstate
*.tfstate.backup
.terraform/
*.tfvars # if they contain sensitive values
crash.log
# Secrets and credentials
.env
*.pem
*.key
credentials
*_credentials.json
# Language-specific
__pycache__/
node_modules/
*.pyc
.venv/
# IDE and OS
.DS_Store
.idea/
.vscode/
Thumbs.dbVerify your .gitignore is actually working — run git status after adding a test .env file to confirm it is excluded. Credentials in Git history are a permanent problem even if you delete the file later.
Common structure mistakes
- Everything in the root directory. Ten
.tffiles at root level with no folder organisation signals unfamiliarity with real codebases. - Application code and infrastructure code mixed together. Keep application logic and deployment configuration in separate folders.
- No separation between environments. A single Terraform file that somehow handles both dev and prod through hardcoded conditionals is harder to maintain and harder to understand.
- Committed
.terraform/directories or.tfstatefiles. These should never be in the repository — check your.gitignorebefore the first commit. - A
main.pyorindex.jsat root level with no further structure. Even for a simple project, asrc/folder with logical subfolders shows better organisation than a flat file.
Summary
- Repository structure communicates professionalism before a line of code is read — invest in it
- Terraform: modules/ for reusable modules, environments/ for environment-specific configuration, .github/workflows/ for CI
- Kubernetes: apps/ for application code, k8s/ for manifests grouped by service, helm/ for Helm charts
- Serverless: src/ for application code, infrastructure/ for Terraform/SAM, tests/ split into unit and integration
- The README architecture decisions section is the most important section — it is where your thinking is visible
- Always verify .gitignore before the first commit — credentials in Git history are a permanent problem