2.1. Project Structure
In this lab you will initialise a local Pulumi state backend and scaffold a new Python project.
Step 1 — Login with a Local Backend
Execute the following command to store your Pulumi state locally:
Verify the login:
You should see User: <local> and the backend URL pointing to your home directory.
Explanation
Before Pulumi can manage any resources it needs a state backend — a place to store the JSON record of every deployed resource and its properties.
| Backend | Where state is stored | Best for |
|---|---|---|
| Pulumi Cloud | Managed SaaS | Teams, audit history, UI |
| Azure Blob Storage | Azure Storage Account | Azure-native teams |
| AWS S3 | S3 bucket | AWS-native teams |
| Google Cloud Storage | GCS bucket | GCP-native teams |
| S3-compatible | MinIO, Cloudflare R2, etc. | Self-hosted / air-gapped |
Local (--local) | ~/.pulumi/stacks/ | Solo dev, this lab |
pulumi login --local points Pulumi at your home directory. No account or network access required. Later in the
workshop you will switch to an Azure Blob Storage backend for team use.
Step 2 — Bootstrap a New Project
Create a new directory and scaffold a Python Pulumi project from the built-in template:
When prompted:
| Prompt | Suggested value |
|---|---|
| Project name | pulumi-basics |
| Project description | (press Enter to accept the default) |
| Stack name | dev |
| Passphrase | (press Enter to leave it empty) |
| Toolchain | pip |
If you left the passphrase empty, set the environment variable so Pulumi does not prompt on every command:
After the scaffold completes, inspect the generated files:
Explanation
pulumi new python scaffolds a complete Python project:
| File | Purpose |
|---|---|
Pulumi.yaml | Declares the project name and runtime. Pulumi reads this to know how to execute the program. |
__main__.py | The entry point. Add, modify, or delete cloud resources by editing this file. |
requirements.txt | Python dependency manifest. Add provider packages here (e.g. pulumi-azure-native). |
venv/ | Virtual environment created by pulumi new. Managed by Pulumi — never activate manually. |
Pulumi.yaml declares the project name and runtime:
__main__.py is intentionally minimal — every resource you manage gets declared here by importing a provider SDK and instantiating resource classes:
The stack config file Pulumi.dev.yaml is created in the project root alongside Pulumi.yaml. In Step 3 you will
move it to a config/ subdirectory.
What to commit
Commit Pulumi.yaml, __main__.py, and requirements.txt. The scaffold already adds venv/ to .gitignore for you.
Step 3 — Move Stack Config to a Subdirectory
By default, Pulumi writes Pulumi.dev.yaml next to Pulumi.yaml in the project root. Add stackConfigDir to keep
stack files in a dedicated config/ folder:
Then add stackConfigDir to Pulumi.yaml:
Verify Pulumi still resolves the stack:
Explanation
stackConfigDir tells Pulumi where to look for Pulumi.<stack>.yaml files. Moving them out of the root keeps
the project tidy as the number of stacks grows — each environment (dev, staging, prod) gets its own file
under config/ rather than cluttering the project root.
What to commit
Commit config/Pulumi.dev.yaml along with the updated Pulumi.yaml. The config/ directory is not gitignored
by default.