DEV Community

Cover image for Stop Copy-Pasting Project Scaffolds - Use a Template Engine
Pablo Ifrán
Pablo Ifrán

Posted on • Originally published at blog.getbp.dev

Stop Copy-Pasting Project Scaffolds - Use a Template Engine

Ever find yourself copying an old project, deleting half the files, doing a find-and-replace for the service name, and praying you didn't miss a reference?

I've been there too many times. Cookiecutter is great, but it's another tool to install. curl | bash scripts work until they don't. And templating tools like degit give you files but no variables.

Blueprint already lets you define your dev environment in a .bp file and apply it across machines. Now it also scaffolds entire projects — with interactive prompts, shared template libraries, and zero extra setup.

The Old Way: blueprint render

Blueprint has had render for a while. Point it at a template directory, pass --var KEY=VALUE for every variable, and it generates files:

blueprint render setup.bp \
  --template @github:org/templates@main:python-service \
  --output ./my-new-api \
  --var APP_NAME=my-new-api \
  --var PORT=8000
Enter fullscreen mode Exit fullscreen mode

This works great for updating existing projects — CI pipelines, re-rendering after a version bump, etc. But for scaffolding a new project? You have to remember every variable, type out the JSON, check the docs. It's not interactive.

The New Way: blueprint template

Scaffold new projects interactively

The new template command flips the interaction model:

blueprint template @github:org/templates@main:python-service \
  --output ./my-new-api
Enter fullscreen mode Exit fullscreen mode

That's it. Blueprint:

  1. Resolves the template — local dir, @github: shorthand, or git URL
  2. Scans every .tmpl file for {{ var "NAME" }}, {{ toValue "NAME" }}, and {{ default "NAME" "fallback" }}
  3. Loads defaults from the template's setup.bp if present
  4. Prompts you for each variable — yellow for required, blue for optional with defaults
  5. Renders the full directory tree into --output, stripping .tmpl extensions

The session looks like this:

─── Template Variables ───

APP_NAME (required): my-new-api
PORT (default: 8000): 9000        ← Enter accepts the default
Enter fullscreen mode Exit fullscreen mode

Shared Template Libraries

Template Library

The real power is sharing templates across your team or org. Create a repository with this structure:

python-service/
  setup.bp              # var PORT 8000  /  var APP_NAME
  Dockerfile.tmpl        # FROM python:3.12\nEXPOSE {{ var "PORT" }}
  entrypoint.sh.tmpl     # exec {{ var "APP_NAME" }}
  config/
    settings.py.tmpl     # PORT = {{ default "PORT" "8000" }}
Enter fullscreen mode Exit fullscreen mode

Anyone on your team can scaffold a new service in one command:

blueprint template @github:myorg/templates@main:python-service \
  --output ./billing-service
Enter fullscreen mode Exit fullscreen mode

No setup.bp required on their side. No documentation to read. No "what variables do I need to pass?" The template tells them.

CI/CD workflows, too

Blueprint's own drift-check workflow template is scaffolded with:

blueprint template @github:elpic/templates@main:actions/github/drift-check \
  --output ./.github \
  --var CHECKS='[{"file":"setup.bp","template":"@github:elpic/templates@main:actions/github/integration/go","against":".github/workflows"}]'
Enter fullscreen mode Exit fullscreen mode

Here CHECKS is a JSON array — a perfect case for --var, which skips the prompt for that variable.

Creating Your Own Template

A blueprint template is just a directory with .tmpl files and an optional setup.bp. Three template functions drive the variables:

Template syntax Prompt behavior
{{ var "NAME" }} Required — no default shown
{{ toValue "NAME" }} Required — no default shown
{{ default "NAME" "val" }} Optional — shows "val" as default

Quick comparison

Here's a minimal Dockerfile.tmpl:

FROM python:3.12-slim
WORKDIR /app
COPY . .
EXPOSE {{ default "PORT" "8000" }}
CMD ["{{ var "APP_NAME" }}"]
Enter fullscreen mode Exit fullscreen mode

And its companion setup.bp:

var PORT 8000
var APP_NAME
Enter fullscreen mode Exit fullscreen mode

Users running blueprint template against this directory get:

PORT (default: 8000):    ← Enter for 8000
APP_NAME (required):     ← must provide
Enter fullscreen mode Exit fullscreen mode

Variable Precedence

Priority Source
1 (highest) Value typed at the prompt
2 --var KEY=VALUE flag
3 var KEY value in template's setup.bp
4 (fallback) {{ default "KEY" "val" }} in template

Why not just use Cookiecutter?

Cookiecutter is excellent, but:

  • It's another install — Python, pipx, or brew. Blueprint is already in your toolchain if you use it for dev environment setup.
  • Template variables require a separate cookiecutter.json — Blueprint discovers them from the template files themselves.
  • Same remote template infra — Blueprint uses the same @github: shorthand and git caching as its other commands. No new mental model.

What's next

Blueprint's template command is in the latest release. The rendering infrastructure is shared with blueprint render, so remote templates are cached, local overrides work, and drift detection is on the roadmap.

If you maintain internal templates at your company — service scaffolds, CI workflows, config generators — give blueprint template a try. One command, no docs to write, no variables to remember.

Top comments (0)