Module 1: Your First Dbt Project
Mini-project: Kiosko's first dbt project
Description
It's time to bring the module's seven lessons together into a single flow, start to finish, from a completely empty folder. You already know what analytics engineering is and where dbt lives inside ELT (lessons 2 and 3), you installed dbt-core and dbt-duckdb (lesson 4), you understood a project's anatomy (lesson 5), you connected it to a real warehouse (lesson 6), and you ran your first model (lesson 7). This mini-project repeats those four hands-on pieces — installation already done, dbt_project.yml, profiles.yml, a model, dbt run — with no pause between steps, so the complete sequence sticks in memory before module 2 starts adding real complexity.
And it adds one new piece, which closes the loop from lesson 2: the first version control step over Kiosko's project. git init, a deliberately thought-out .gitignore (not everything in the folder deserves to be versioned), and the first commit. Analytics engineering, by the definition this module opened with, means treating data transformation with the same practices a software team uses — and of those practices, version control is the first one, and the one that holds up all the others.
Connection to the module. This project introduces no new SQL concept — it's the synthesis of lessons 4 through 7, run again with no interruptions, plus this whole guide's first git init. And it sets the exact stage for module 2: the kiosko_analytics/ folder you assemble here is the one you're going to keep using, module by module, all the way to module 8's capstone — the models/example/ folder you rebuild in this project is, on purpose, the same one you're going to delete entirely as soon as you declare Kiosko's first source()s.
An analogy: dress rehearsal, before opening night
Before a theater opening night, the cast runs a dress rehearsal: the whole play, start to finish, with no pauses to fix minor mistakes, with the director not interrupting every two minutes to explain again why a scene works the way it does. The goal isn't to learn something new — every actor already knows their part — it's to confirm the complete sequence flows, that the transitions between scenes have no gaps, and that the cast can carry the whole play without outside help.
This mini-project is your module 1 dress rehearsal. You're not going to learn any concept you haven't already seen in the seven previous lessons — you're going to confirm you can run the whole sequence, from an empty folder to a working, versioned dbt project, with nobody reminding you of the next step. That fluency is exactly what you're going to need in module 2, when the project starts growing with real pieces.
The material: the complete sequence, with no pauses
Start from a new folder, to simulate exactly what you'd do on a real project from scratch (if you already have kiosko_analytics/ from earlier lessons, you can use a folder with a different name for this exercise, or simply repeat the sequence on the same one):
# 1. the environment (already installed in lesson 4; activate it if this is a new terminal)
source kiosko_env/bin/activate
# 2. the project folder (lesson 5)
mkdir -p kiosko_analytics/models/example
cd kiosko_analytics
export DBT_PROFILES_DIR=$(pwd)
dbt_project.yml, exactly as in lesson 5:
# dbt_project.yml
name: "kiosko_analytics"
version: "1.0.0"
config-version: 2
profile: "kiosko_analytics"
model-paths: ["models"]
seed-paths: ["seeds"]
test-paths: ["tests"]
macro-paths: ["macros"]
snapshot-paths: ["snapshots"]
target-path: "target"
clean-targets:
- "target"
- "dbt_packages"
models:
kiosko_analytics:
+materialized: view
profiles.yml, exactly as in lesson 6:
# profiles.yml
kiosko_analytics:
target: dev
outputs:
dev:
type: duckdb
path: kiosko.duckdb
threads: 4
And the first model, exactly as in lesson 7:
-- models/example/my_first_dbt_model.sql
select
1 as id,
'kiosko_analytics project is alive' as status
union all
select
2 as id,
'connected to kiosko.duckdb' as status
The reference solution, verified
Part 1 — Confirm the connection and run the model
dbt debug
dbt run
What to expect. dbt debug ends with All checks passed! (identical to lesson 6's). dbt run ends with:
Done. PASS=1 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=1
If either command fails, don't move on to Part 2 — go back to lesson 5 or 6, depending on which of the two files (dbt_project.yml or profiles.yml) is the cause. This project's complete sequence only makes sense on top of a foundation that already works.
Part 2 — Verify twice, with different tools
You already saw in lesson 7 that you run dbt run twice to confirm idempotency. Here you verify the result with two different tools, to confirm the object dbt created is a real warehouse object, not something only dbt "understands":
dbt run # second run, on the same unchanged model
dbt show --select my_first_dbt_model --output json
What to expect. The second dbt run ends again in PASS=1 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=1 — the exact same result as the first. dbt show returns:
{
"node": "my_first_dbt_model",
"show": [
{
"id": 1,
"status": "kiosko_analytics project is alive"
},
{
"id": 2,
"status": "connected to kiosko.duckdb"
}
]
}
Part 3 — The project's first version control
This is the module's new piece. Before versioning anything, decide with judgment what should not go into the repository — not everything in the folder is source code:
cat > .gitignore << 'EOF'
target/
dbt_packages/
logs/
*.duckdb
*.duckdb.wal
.user.yml
profiles.yml
EOF
Every line of this .gitignore answers a concrete reason, not a mechanical copy from a template:
target/,dbt_packages/,logs/— folders dbt generates (lesson 5). Versioning them would be like versioning a Python project's__pycache__: they regenerate on their own, and saving them in git only adds noise with no benefit.*.duckdb,*.duckdb.wal— the database file itself. A binary file that grows with every run doesn't belong in a code repository — what matters to version is theSELECTthat produces it (the.sqlfile), not the binary result. Anyone with the project can rebuildkiosko.duckdbfrom scratch withdbt run..user.yml— a file dbt generates automatically with a random identifier, used only for anonymous usage statistics. It isn't project configuration — it's a file specific to your machine, with no value to the rest of the team.profiles.yml— as you saw in lesson 6, this file normally holds credentials, and that's why it's almost never versioned, even when (as in DuckDB's case) it has no real secret in it.path: kiosko.duckdbdepends on the machine you run the project on just as much as any credential would — versioning it would be a bad habit, even though today it causes no concrete harm.
Now, initialize the repository and make the first commit:
git init
git add dbt_project.yml models .gitignore
git commit -m "First dbt project: kiosko_analytics scaffolding"
What to expect.
Initialized empty Git repository in /path/to/your/kiosko_analytics/.git/
[master (root-commit) af0b710] First dbt project: kiosko_analytics scaffolding
3 files changed, 34 insertions(+)
create mode 100644 .gitignore
create mode 100644 dbt_project.yml
create mode 100644 models/example/my_first_dbt_model.sql
(The commit's short identifier, af0b710 in this case, is going to be different on your machine — it's a hash generated from the exact content and the moment of the commit, never the same twice.) Confirm the working tree came out clean:
git status
What to expect.
On branch master
nothing to commit, working tree clean
No generated file — not kiosko.duckdb, not target/, not logs/, not .user.yml — shows up as pending. The .gitignore you wrote did exactly its job: only the project's real source code — three files, dbt_project.yml, .gitignore, and the model — ended up versioned.
Diagram: the mini-project's complete flow
flowchart TD
A["empty folder"] --> B["mkdir models/example\ndbt_project.yml (lesson 5)"]
B --> C["profiles.yml (lesson 6)"]
C --> D["dbt debug -> All checks passed!"]
D --> E["my_first_dbt_model.sql (lesson 7)"]
E --> F["dbt run -> PASS=1"]
F --> G["dbt run again -> PASS=1 (idempotent)"]
G --> H["dbt show -> confirms the content"]
H --> I[".gitignore + git init + git commit"]
I --> J["git status -> clean tree"]
Common mistakes
Versioning kiosko.duckdb by accident, with git add .. What happens: someone uses git add . (add everything in the folder) instead of naming files explicitly, and the binary kiosko.duckdb file — which can weigh hundreds of kilobytes or more, and grows with every run — ends up in git's history. Why it happens: git add . is faster to type than listing files one by one, and without a .gitignore already in place, git has no way of knowing what to exclude. How to spot it: git status after the commit shows kiosko.duckdb as a tracked file; git log --stat shows that file changing size on every later commit. How to fix it: always write the .gitignore before the first git add — as this mini-project does — and prefer naming files explicitly (git add dbt_project.yml models .gitignore, as in the worked example) instead of git add ., at least until you trust your .gitignore is complete.
Thinking .gitignore deletes or affects files that were already tracked. What happens: someone adds *.duckdb to .gitignore after having committed kiosko.duckdb by mistake, and is surprised git status keeps showing it as modified on every run. Why it happens: .gitignore only tells git which new, untracked files to ignore — it has no retroactive effect on files already part of the history. How to spot it: if a file you added to .gitignore keeps showing up in git status, that's a sign it was already tracked from before. How to fix it: besides adding it to .gitignore, you have to explicitly remove it from tracking with git rm --cached kiosko.duckdb (this drops the file from version control, but leaves it intact on your disk) and make a new commit with that change.
Believing a generic .gitignore found online is always enough. What happens: someone copies a dbt .gitignore found online without checking whether it excludes profiles.yml — many generic templates assume that file lives at ~/.dbt/ (the standard convention lesson 6 explained, and that this project breaks on purpose) and so don't even mention it. Why it happens: a generic template can't anticipate design decisions specific to your project, like keeping profiles.yml inside the versioned folder. How to spot it: check your own .gitignore against the list of generated or sensitive files your specific project produces — don't assume someone else's template already thought of your case. How to fix it: this mini-project's .gitignore is written line by line with the reason for each entry — that discipline (understanding why each line is there, not just copying it) is, again, the same one lesson 2's definition of analytics engineering demands.
Exercises
Exercise 1 — Rebuild the project in a new folder. Without looking at this lesson's worked example, write from memory (or from your own notes) the three files — dbt_project.yml, profiles.yml, models/example/my_first_dbt_model.sql — in a folder called kiosko_ensayo/, and confirm with dbt debug and dbt run that it works the same as the original.
See solution
If you followed the sequence from lessons 5, 6, and 7, the result should be exactly the same: dbt debug ends in All checks passed!, dbt run ends in Done. PASS=1 WARN=0 ERROR=0 SKIP=0 NO-OP=0 REUSED=0 TOTAL=1. If something fails, compare your dbt_project.yml against lesson 5's field by field — the most common mistake in this exercise is forgetting that profile: in dbt_project.yml must exactly match profiles.yml's top-level key. Completing this exercise unassisted is the sign the dress rehearsal did its job.
Exercise 2 — Trigger the versioned-kiosko.duckdb mistake, and fix it. In your practice folder (not your real project), run git add . instead of naming files explicitly, after having run dbt run at least once (so kiosko.duckdb exists). Confirm with git status that it ended up tracked, and then fix it with git rm --cached.
See solution
git add .
git status # kiosko.duckdb, target/, logs/ will show up as "Changes to be committed"
git reset # undoes the "add" without touching the files on disk
git rm --cached kiosko.duckdb # if it was already committed; if it's only staged, "git reset" is enough
With a .gitignore already in place (like this lesson's), git add . respects the exclusion rules and doesn't add kiosko.duckdb again — the key point of the exercise is noticing the problem only shows up when .gitignore doesn't exist yet at the moment of the add, reinforcing why the order of steps (.gitignore before the first add) matters just as much as its content.
Exercise 3 — Argue why versioning the .sql is enough, and the .duckdb isn't needed. In 2-3 sentences, using what you know from this lesson and lesson 7, explain why a teammate who clones Kiosko's repository (with this lesson's .gitignore applied) loses nothing important, even though kiosko.duckdb is never in the repository.
See solution
A teammate who clones the repository receives dbt_project.yml and models/example/my_first_dbt_model.sql — the complete SELECT that defines the model — and just by running dbt run (after creating their own profiles.yml, since that isn't versioned either) rebuilds kiosko.duckdb from scratch, with exactly the same content the original file had. The .duckdb file is a derived result of the source code (the .sql), not the source code itself — it's the same relationship that exists between a .py file and the compiled .pyc file Python generates: versioning the derived result is redundant, because it can always be regenerated from the source, and doing so only adds unnecessary weight to the repository.
Summary and next step
In this mini-project you rebuilt module 1's complete flow, with no pauses: dbt_project.yml, profiles.yml, a first model, dbt debug, dbt run (twice, confirming idempotency), dbt show as an additional check — and added this project's new piece: a deliberately thought-out .gitignore, git init, and Kiosko's project's first versioned commit, with a clean working tree confirmed by git status.
With this you close module 1. You now have the complete vocabulary — analytics engineering, ELT's T, a dbt project's anatomy — and a real project, kiosko_analytics/, working end to end and under version control. That reflex — no generated file gets versioned, everything that gets versioned has a reason to be there — is exactly the discipline you're going to need, with much more force, when the project grows with dozens of real models.
Where you go next. Module 2 completely deletes the models/example/ folder you built here, and replaces it with the first real contact between dbt and Kiosko's data: you're going to declare orders, events, stores, and products as source() in a sources.yml file, pointing directly at the raw files data-engineering-foundations-guide already generated — without loading them into any intermediate table first — and you're going to build the first real staging models: stg_orders, stg_events, stg_stores, stg_products. The scaffolding you built in this module 1 — dbt_project.yml, profiles.yml, the dbt debug/dbt run ritual — doesn't change at all; the only thing that changes, from here on, is that the models are finally going to touch real data.
Resources
- dbt Developer Hub — "About dbt projects," the overview confirming the complete structure you rehearsed in this mini-project. docs.getdbt.com/docs/build/projects. In English.
- Git — official
gitignoredocumentation, including the pattern syntax (*.duckdb,target/) used in this lesson's.gitignore. git-scm.com/docs/gitignore. In English. - dbt Labs — "What is analytics engineering?," the same source from lesson 2, useful to re-read now that you've completed your first versioned commit of a real dbt project. getdbt.com/blog/what-is-analytics-engineering. In English.