From 9f2788e4cf76031c20d4f04f1a674d4bee2b77ec Mon Sep 17 00:00:00 2001 From: h Date: Sat, 19 Sep 2026 22:02:34 +0200 Subject: [PATCH] Initial template --- .gitignore | 2 + README.md | 10 ++++ copier.yml | 40 ++++++++++++++++ .../{{ _copier_conf.answers_file }}.jinja | 1 + template/{{ part_dir }}/.env.example.jinja | 2 + template/{{ part_dir }}/Makefile.jinja | 47 +++++++++++++++++++ .../docker-compose.override.yml.example.jinja | 12 +++++ .../{{ part_dir }}/docker-compose.yml.jinja | 12 +++++ ...out == 'root' %}README.md{% endif %}.jinja | 12 +++++ 9 files changed, 138 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 copier.yml create mode 100644 template/{{ _copier_conf.answers_file }}.jinja create mode 100644 template/{{ part_dir }}/.env.example.jinja create mode 100644 template/{{ part_dir }}/Makefile.jinja create mode 100644 template/{{ part_dir }}/docker-compose.override.yml.example.jinja create mode 100644 template/{{ part_dir }}/docker-compose.yml.jinja create mode 100644 template/{{ part_dir }}/{% if layout == 'root' %}README.md{% endif %}.jinja diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4befed3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +.idea diff --git a/README.md b/README.md new file mode 100644 index 0000000..277729d --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# blank + +Copier template for a part in any stack that has no template of its own, either as a `backend/` or `frontend/` part next to other parts or alone at the root of the project (`layout`). It brings nothing but the part contract, the code and the `Dockerfile` are yours. + +Ships `docker-compose.yml` with root-relative paths and one service under its own profile and `services`, `docker-compose.override.yml.example` (the `caddy` network alias, copied by `make env`), a `Makefile` with empty `fmt` and `check`, `build`, `pre-deploy` that rebuilds the image only when the part changed and an empty `post-deploy`, and `.env.example`. Applied on top of the `infra` template, `layout: parts` or `layout: single` respectively. The `caddy` template expects the backend to listen on 8080 and the frontend on 3000. + +```sh +copier copy --trust -a .copier/backend.yml -d project_name="My Thing" -d part=backend +copier update -a .copier/backend.yml +``` diff --git a/copier.yml b/copier.yml new file mode 100644 index 0000000..1248e85 --- /dev/null +++ b/copier.yml @@ -0,0 +1,40 @@ +_subdirectory: template +_templates_suffix: .jinja +_answers_file: .copier/part.yml +_envops: + keep_trailing_newline: true + +project_name: + type: str + help: Project name + +project_slug: + type: str + help: Project slug, snake_case + default: "{{ project_name | lower | replace(' ', '_') | replace('-', '_') | replace('.', '_') }}" + +part: + type: str + help: Which part this is + choices: + backend: backend + frontend: frontend + default: backend + +layout: + type: str + help: Where the part lives + choices: + In its own directory next to other parts: part + At the root of the project: root + default: part + +service: + type: str + help: Compose service name, also the profile and the caddy alias suffix + default: "{{ 'api' if part == 'backend' else 'frontend' }}" + +part_dir: + type: str + when: false + default: "{{ part if layout == 'part' else '.' }}" diff --git a/template/{{ _copier_conf.answers_file }}.jinja b/template/{{ _copier_conf.answers_file }}.jinja new file mode 100644 index 0000000..d0f5d97 --- /dev/null +++ b/template/{{ _copier_conf.answers_file }}.jinja @@ -0,0 +1 @@ +{{ _copier_answers|to_nice_yaml -}} diff --git a/template/{{ part_dir }}/.env.example.jinja b/template/{{ part_dir }}/.env.example.jinja new file mode 100644 index 0000000..8ead95f --- /dev/null +++ b/template/{{ part_dir }}/.env.example.jinja @@ -0,0 +1,2 @@ +{% if layout == 'root' %}COMPOSE_PROFILES=services +{% endif %} \ No newline at end of file diff --git a/template/{{ part_dir }}/Makefile.jinja b/template/{{ part_dir }}/Makefile.jinja new file mode 100644 index 0000000..99b0cde --- /dev/null +++ b/template/{{ part_dir }}/Makefile.jinja @@ -0,0 +1,47 @@ +COMPOSE := {% if layout == 'part' %}cd .. && {% endif %}docker compose +IMAGE := {{ project_slug }}/{{ part }} +changed = ! git diff --quiet $(1) -- $(2) 2>/dev/null +stamp = { git update-ref $(1) HEAD 2>/dev/null || true; } + +.PHONY: {% if layout == 'root' %}env recreate rebuild restart logs down deploy {% endif %}fmt check build pre-deploy post-deploy + +fmt: + @: + +check: + @: + +build: + $(COMPOSE) build {{ service }} + +pre-deploy: + @if $(call changed,refs/deploy/{{ part }},.) || ! docker image inspect $(IMAGE) >/dev/null 2>&1; then \ + $(MAKE) build && $(call stamp,refs/deploy/{{ part }}); \ + fi + +post-deploy: + @: +{% if layout == 'root' %} +env: + @test -f .env || cp .env.example .env + @test -f docker-compose.override.yml || cp docker-compose.override.yml.example docker-compose.override.yml + +recreate: + $(COMPOSE) up -d --force-recreate + +rebuild: build + $(COMPOSE) up -d + +restart: + $(COMPOSE) restart + +logs: + $(COMPOSE) logs -f --tail=100 + +down: + $(COMPOSE) down + +deploy: pre-deploy + $(COMPOSE) up -d + $(MAKE) post-deploy +{% endif %} \ No newline at end of file diff --git a/template/{{ part_dir }}/docker-compose.override.yml.example.jinja b/template/{{ part_dir }}/docker-compose.override.yml.example.jinja new file mode 100644 index 0000000..d22cc24 --- /dev/null +++ b/template/{{ part_dir }}/docker-compose.override.yml.example.jinja @@ -0,0 +1,12 @@ +{%- set dash = project_slug | replace('_', '-') -%} +services: + {{ service }}: + networks: + default: {} + caddy: + aliases: + - {{ dash }}-{{ service }} + +networks: + caddy: + external: true diff --git a/template/{{ part_dir }}/docker-compose.yml.jinja b/template/{{ part_dir }}/docker-compose.yml.jinja new file mode 100644 index 0000000..44b6e0c --- /dev/null +++ b/template/{{ part_dir }}/docker-compose.yml.jinja @@ -0,0 +1,12 @@ +services: + {{ service }}: + build: {{ part_dir }} + image: {{ project_slug }}/{{ part }} + profiles: [{{ service }}, services] + restart: unless-stopped + env_file: + - path: .env + required: false +{% if layout == 'part' %} - path: {{ part }}/.env + required: false +{% endif %} \ No newline at end of file diff --git a/template/{{ part_dir }}/{% if layout == 'root' %}README.md{% endif %}.jinja b/template/{{ part_dir }}/{% if layout == 'root' %}README.md{% endif %}.jinja new file mode 100644 index 0000000..7873cb3 --- /dev/null +++ b/template/{{ part_dir }}/{% if layout == 'root' %}README.md{% endif %}.jinja @@ -0,0 +1,12 @@ +# {{ project_name }} + +Runs in Docker, locally the same way as in production. `COMPOSE_PROFILES` in `.env` selects the services that run on this machine. `docker-compose.override.yml` holds what differs between machines: published ports and the aliases on the external `caddy` network. + +```sh +make env +make recreate +``` + +`make env` creates `.env` and `docker-compose.override.yml` from their examples. `make recreate` starts the selected profiles, `make rebuild` builds the image first, `make logs`, `make restart` and `make down` do what they say. + +To deploy, `git pull && make deploy`. It runs `make pre-deploy`, which rebuilds the image only when something in the repository changed, then `docker compose up -d`. A deploy system with its own `up` step calls `make pre-deploy` before it and `make post-deploy` after. What was built is remembered as `refs/deploy/*` in the clone.