Add a Docker dev service to this project. Supported services: Redis, RabbitMQ, PostgreSQL, MySQL/MariaDB, MongoDB. Writes Docker Compose and Taskfile configs to .devtools/<service>/ (one subfolder per service instance).
Each service gets its own subdirectory under .devtools/ containing its compose fragment,
Taskfile, and per-service .env (credentials for that service only). Root .devtools/.env
holds only project-level vars (COMPOSE_PROJECT_NAME, COMPOSE_PROFILES).
The skill asks for port, image version, and credentials before writing anything. A
configuration summary is shown for confirmation before any file is created. The skill is
safe to re-run: it detects existing installs and either renames the existing instance (to free
the slot), installs an aliased second instance, or exits cleanly. All credentials land in
.devtools/<service>/.env which is gitignored via **/.env in .devtools/.gitignore.
</objective>
All template paths below are relative to SKILL_RAW_BASE (defined below) — fetch them
remotely at runtime. The target project's CWD is separate — do NOT mix these paths.
| Service | Compose Template | Taskfile Template | Metadata |
|---|
| redis | compose-templates/redis/redis.compose.yml | taskfile-templates/redis/redis.Taskfile.yml | compose-templates/redis/metadata.json |
| rabbitmq | compose-templates/rabbitmq/rabbitmq.compose.yml | taskfile-templates/rabbitmq/rabbitmq.Taskfile.yml | compose-templates/rabbitmq/metadata.json |
| postgres | compose-templates/postgres/postgres.compose.yml | taskfile-templates/postgres/postgres.Taskfile.yml | compose-templates/postgres/metadata.json |
| mysql | compose-templates/mysql/mysql.compose.yml | taskfile-templates/mysql/mysql.Taskfile.yml | compose-templates/mysql/metadata.json |
| mongodb | compose-templates/mongodb/mongodb.compose.yml | taskfile-templates/mongodb/mongodb.Taskfile.yml | compose-templates/mongodb/metadata.json |
| monitoring | compose-templates/monitoring/monitoring.compose.yml | taskfile-templates/monitoring/monitoring.Taskfile.yml | compose-templates/monitoring/metadata.json |
Root Taskfile template: taskfile-templates/root/Taskfile.yml
Templates are fetched at runtime from the skill's source repository. All template reads
below use this base URL (so the skill works when installed via npx skills add — only
SKILL.md is installed locally):
SKILL_RAW_BASE=https://raw.githubusercontent.com/Cyboooooorg/dev-tools/main
Fetch any template with:
curl -fsSL "${SKILL_RAW_BASE}/<template-path>"
If curl is unavailable, use wget -qO- "${SKILL_RAW_BASE}/<template-path>".
Compose templates already use ${ENV_VAR} references (e.g. ${REDIS_PORT}) — they do NOT
contain {{TOKEN}} placeholders. For standard installs, copy templates verbatim. String
substitution (renaming env vars, container names, volume names) is only required for
alias/multi-instance installs.
</context>
Determine the service name:
$ARGUMENTS is not empty, SERVICE=$ARGUMENTS (normalize to lowercase).$ARGUMENTS is empty, skip to Step 3 to ask the user to select a service.Check whether the service is already installed:
test -f .devtools/${SERVICE}/${SERVICE}.compose.yml
The service is already installed. Inform the user:
"
${SERVICE}is already installed at.devtools/${SERVICE}/."
First, offer to rename the existing instance (D-15). This lets the user free up the
${SERVICE} slot for a fresh install, rather than being forced to install an alias:
Ask:
"Before adding a new instance, rename the existing '${SERVICE}' to free up the slot? Enter a rename suffix (e.g. 'cache' → renames to '${SERVICE}-cache'), or press Enter to skip: [skip]"
If the user provides a non-empty rename suffix (e.g. cache):
Set rename variables:
RENAME_ALIAS=<rename suffix> (lowercase, letters/numbers/hyphens only)RENAME_SLUG=${SERVICE}-${RENAME_ALIAS} (e.g. redis-cache)RENAME_SNAKE=${SERVICE}_${RENAME_ALIAS} (e.g. redis_cache)RENAME_PREFIX=<SERVICE_UPPER>_<RENAME_ALIAS_UPPER> (e.g. REDIS_CACHE)Rename the folder:
if [ -d ".devtools/${RENAME_SLUG}" ]; then
echo "Error: .devtools/${RENAME_SLUG} already exists. Choose a different alias." && exit 1
fi
mv .devtools/${SERVICE} .devtools/${RENAME_SLUG}
Rename files inside the folder:
mv .devtools/${RENAME_SLUG}/${SERVICE}.compose.yml .devtools/${RENAME_SLUG}/${RENAME_SLUG}.compose.yml
If ${SERVICE}.Taskfile.yml exists in the folder:
mv .devtools/${RENAME_SLUG}/${SERVICE}.Taskfile.yml .devtools/${RENAME_SLUG}/${RENAME_SLUG}.Taskfile.yml
Apply string substitutions inside the renamed compose and Taskfile (same substitution logic as Step 12 alias installs, applied to the existing file content):
${SERVICE}: → ${RENAME_SNAKE}:-${SERVICE} → -${RENAME_SLUG}${SERVICE}_data: → ${RENAME_SNAKE}_data:${SERVICE}_data → ${RENAME_SNAKE}_data${SERVICE}_net: → ${RENAME_SNAKE}_net:${SERVICE}_net → ${RENAME_SNAKE}_net${SERVICE_UPPER}_* → ${RENAME_PREFIX}_*
(e.g. REDIS_PORT → REDIS_CACHE_PORT, REDIS_PASSWORD → REDIS_CACHE_PASSWORD)${SERVICE}.compose.yml → ${RENAME_SLUG}.compose.ymlUpdate .devtools/compose.yml include reference:
Read .devtools/compose.yml, find the line:
- path: ${SERVICE}/${SERVICE}.compose.yml
Replace with:
- path: ${RENAME_SLUG}/${RENAME_SLUG}.compose.yml
Update .devtools/Taskfile.yml include reference:
Read .devtools/Taskfile.yml, find the block:
${SERVICE}:
taskfile: ${SERVICE}/${SERVICE}.Taskfile.yml
Replace with:
${RENAME_SLUG}:
taskfile: ${RENAME_SLUG}/${RENAME_SLUG}.Taskfile.yml
Update .devtools/${RENAME_SLUG}/.env env var keys (D-16):
For each env var key in the file that starts with ${SERVICE_UPPER}_ (e.g. REDIS_):
## REPLACED as an inline comment on the old line.${RENAME_PREFIX}_<SUFFIX>=<value>.REDIS_PORT=6379 ## REPLACED
REDIS_CACHE_PORT=6379
REDIS_PASSWORD=secret ## REPLACED
REDIS_CACHE_PASSWORD=secret
Announce rename completion and proceed (D-17): Output:
"Existing ${SERVICE} renamed to ${RENAME_SLUG}. Now installing new ${SERVICE} instance..."
Proceed to Step 3 with MODE=standard, SERVICE=${SERVICE}. The .devtools/${SERVICE}/
slot is now free — the new install will land there. (D-18: all rename operations above are
completed before any new files are written.)
If the user presses Enter without providing a suffix:
Ask: "Add another instance with an alias, or cancel? [alias/cancel]"
"Nothing written. Exiting." and stop.cache, session):
ALIAS=<alias> (lowercase, letters/numbers/hyphens only).SERVICE_SLUG=${SERVICE}-${ALIAS} (e.g. redis-cache).SERVICE_SNAKE=${SERVICE}_${ALIAS} (e.g. redis_cache) — used for YAML service keys, volume keys, network keys.ENV_PREFIX=<SERVICE_UPPER>_<ALIAS_UPPER> (e.g. REDIS_CACHE) — used for env var names.test -f .devtools/${SERVICE_SLUG}/${SERVICE_SLUG}.compose.yml
"${SERVICE_SLUG} is already installed — nothing to do." and stop. (MERGE-04)MODE=alias and continue to Step 3.Only run this step if .devtools/ does not yet exist.
Check:
test -d .devtools
If .devtools/ exists → skip to Step 3.
If .devtools/ does not exist → proceed:
"Creating .devtools/ directory...".mkdir -p .devtools
.devtools/.gitignore with exactly this content (do not add any other entries):
.env
**/.env
This covers both the root .devtools/.env and all per-service .devtools/<slug>/.env files.
Do NOT attempt to migrate any existing flat .devtools/*.compose.yml files — new installs
always use the subfolder layout; old flat files continue to work as-is. (D-13: no migration)"Project name for Docker namespacing? [default: <derive from git remote or pwd>]"
basename $(git remote get-url origin 2>/dev/null || echo $(pwd)) | sed 's/\.git$//'COMPOSE_PROJECT_NAME=<user answer or default>.If .devtools/.env already exists and contains COMPOSE_PROJECT_NAME, skip the project
name question entirely (D-20).
If SERVICE is already set from $ARGUMENTS, skip this step.
Ask the user:
"Which service would you like to add? (redis / rabbitmq / postgres / mysql / mongodb)"
SERVICE=<user answer> (normalize to lowercase).
"Service '<answer>' is not supported. Supported: redis, rabbitmq, postgres, mysql, mongodb."and stop.
Return to Step 1 to run the merge detection check for this service before continuing.
Read the metadata file for the selected service from the skill repo using:
curl -fsSL "${SKILL_RAW_BASE}/compose-templates/${SERVICE}/metadata.json"
Extract:
parameters[] — list of { name, default, env_var, token } entriesui_companion — present or absentexporter — present or absent (used later for monitoring)Store these for use in Step 5.
Ask each question individually using AskUserQuestion. Show the inline default on every
question as [default: X]. Accept the user's answer or use the default if they press enter.
Port? [default: <port from metadata>]
ANSWERS[port].Image version/tag? [default: <version from metadata>]
ANSWERS[version].(Port default: [default: 6379])
3. "Password? [optional — press enter to skip]"
ANSWERS[password]="" (empty string — Redis runs without auth).ANSWERS[password]=<user input>.(Port default: [default: 5672])
3. "Username? [default: admin]" → ANSWERS[username]
4. "Password? (required — no default)" → ANSWERS[password] (must be non-empty; re-ask if blank)
5. "Management UI port? [default: 15672]" → ANSWERS[ui_port]
(Note: RabbitMQ management UI is always-on — bundled in the rabbitmq:-management image.
Step 6 "Enable UI companion?" is SKIPPED for RabbitMQ.)*
(Port default: [default: 5432])
3. "Username? [default: postgres]" → ANSWERS[username]
4. "Password? (required — no default)" → ANSWERS[password] (re-ask if blank)
5. "Database name? [default: app]" → ANSWERS[db_name]
(Port default: [default: 3306])
3. "Username? [default: app]" → ANSWERS[username]
4. "Password? (required — no default)" → ANSWERS[password] (re-ask if blank)
5. "Database name? [default: app]" → ANSWERS[db_name]
6. "Root password? (required — no default)" → ANSWERS[root_password]
(MYSQL_ROOT_PASSWORD — required by MariaDB/MySQL even though it has token: null in metadata.json)
7. (MariaDB is the default image variant. MariaDB 11 ≈ MySQL 8 compatible. If user needs MySQL:
they can override the version tag to use mysql:8 instead.)
(Port default: [default: 27017])
3. "Username? [default: admin]" → ANSWERS[username]
4. "Password? (required — no default)" → ANSWERS[password] (re-ask if blank)
Skip this step entirely for rabbitmq — its management UI is always-on (handled in Step 5).
For all other services (redis, postgres, mysql, mongodb) that have a ui_companion entry
in their metadata:
Ask: "Enable UI companion? [y/N]" (default: N)
If No → set ANSWERS[ui_enabled]=false and skip to Step 7.
If Yes → set ANSWERS[ui_enabled]=true.
Ask: "Enable auth on UI? [y/N]" (default: N) → ANSWERS[ui_auth]=<true/false>
Service-specific follow-ups when UI is enabled:
Ask: "pgAdmin login email? [default: [email protected]]" → ANSWERS[pgadmin_email]
Ask: "pgAdmin login password? (required)" → ANSWERS[pgadmin_password] (re-ask if blank)
(No additional questions — phpMyAdmin connects using MYSQL_USER / MYSQL_PASSWORD.)
If ANSWERS[ui_auth]=true:
Ask: "Mongo Express username? [default: admin]" → ANSWERS[me_username]
Ask: "Mongo Express password? (required)" → ANSWERS[me_password] (re-ask if blank)
(RedisInsight authenticates via the Redis password already captured in Step 5.)
Ask: "Also set up Taskfile tasks? [Y/n]" (default: Y) → ANSWERS[taskfile]=<true/false>
Ask: "Also install monitoring (Grafana + Prometheus)? [y/N]" (default: N) → ANSWERS[monitoring]=<true/false>
If ANSWERS[monitoring]=true:
Ask: "Grafana admin password? [default: admin]" → ANSWERS[grafana_password]
(Default is intentionally weak — surfaced here so user can change it.)
Display a summary table of all collected values. Mask passwords with ****.
Example layout (adapt to the actual service and answers):
Service: redis
Mode: standard install (or: alias install as "redis-cache")
─────────────────────────────────────────────
Port: 6379
Version: 7
Password: ****
UI companion: disabled
Taskfile tasks: yes
Monitoring: no
─────────────────────────────────────────────
Files to write:
.devtools/.gitignore (first install only)
.devtools/redis/redis.compose.yml (or .devtools/redis-cache/redis-cache.compose.yml for alias)
.devtools/redis/redis.Taskfile.yml (if Taskfile=yes)
.devtools/redis/.env (service credentials only)
.devtools/compose.yml (created or appended)
.devtools/Taskfile.yml (created on first install only)
.devtools/.env (COMPOSE_PROJECT_NAME + COMPOSE_PROFILES, first install only)
.devtools/.env.example (updated, first install only)
Ask: "Write these files? [Y/n]"
"Cancelled. Nothing written." and stop.Do NOT write any files before this confirmation. Steps 9–13 perform all writes.
User confirmed "Yes" at Step 8. Proceed with file writes.
Fetch the compose template from the skill repo:
curl -fsSL "${SKILL_RAW_BASE}/compose-templates/${SERVICE}/${SERVICE}.compose.yml"
For standard install (MODE=standard): Copy the file content verbatim.
Special case — Redis, no password: If SERVICE=redis and ANSWERS[password] is empty
string (user pressed enter to skip), after loading the template content:
--requirepass ${REDIS_PASSWORD} argument from the command: line.-a "${REDIS_PASSWORD}" from the healthcheck.test command.
Apply these modifications to the in-memory content before writing (see write destination below).For alias install (MODE=alias): Before writing, perform all string substitutions in the
file content — see Step 12 for the complete substitution map. Apply substitutions to the
in-memory copy before writing. Do NOT modify the original template file.
First, create the service subdirectory:
mkdir -p .devtools/${SERVICE_SLUG}
Where ${SERVICE_SLUG} equals ${SERVICE} for standard installs (e.g. redis) and
${SERVICE}-${ALIAS} for alias installs (e.g. redis-cache). (D-01/D-02)
Write to the target project:
.devtools/${SERVICE_SLUG}/<filename>.compose.yml
Where <filename> is ${SERVICE} for standard, or ${SERVICE_SLUG} for alias
(e.g. .devtools/redis-cache/redis-cache.compose.yml). (D-03/D-04)
Note on env_file: .env: The compose template contains env_file: .env — Docker Compose
include: resolves env_file paths relative to the included file's directory. Since the
compose file lives in .devtools/${SERVICE_SLUG}/, env_file: .env automatically resolves
to .devtools/${SERVICE_SLUG}/.env. No template changes are required. (D-07)
Fetch the per-service Taskfile template from the skill repo:
curl -fsSL "${SKILL_RAW_BASE}/taskfile-templates/${SERVICE}/${SERVICE}.Taskfile.yml"
For alias install: Perform string substitutions (see Step 12) on the in-memory copy.
Write to the target project (subfolder already created by Step 9a mkdir):
.devtools/${SERVICE_SLUG}/<filename>.Taskfile.yml
Where <filename> is ${SERVICE} for standard, or ${SERVICE_SLUG} for alias
(e.g. .devtools/redis-cache/redis-cache.Taskfile.yml). (D-03/D-04)
Note: Always write the per-service file (Step 9) BEFORE updating compose.yml (Step 11).
This ensures compose.yml never references a file that failed to write.
The service subdirectory was created by mkdir -p in Step 9a. Write service credentials
and config to .devtools/${SERVICE_SLUG}/.env (create if not exists, append if exists):
# ── <SERVICE> ───────────────────────────────────────
<ENV_VAR_NAME>=<value>
...
For standard install — env var names from metadata env_var field:
| Service | Env vars to write |
|---|---|
| redis | REDIS_PORT, REDIS_VERSION, REDIS_PASSWORD (empty string if skipped) |
| rabbitmq | RABBITMQ_PORT, RABBITMQ_VERSION, RABBITMQ_USERNAME, RABBITMQ_PASSWORD, RABBITMQ_UI_PORT |
| postgres | POSTGRES_PORT, POSTGRES_VERSION, POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, POSTGRES_UI_PORT (if UI enabled), PGADMIN_DEFAULT_EMAIL (if UI enabled), PGADMIN_DEFAULT_PASSWORD (if UI enabled) |
| mysql | MYSQL_PORT, MYSQL_VERSION, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, MYSQL_ROOT_PASSWORD, MYSQL_UI_PORT (if UI enabled) |
| mongodb | MONGODB_PORT, MONGODB_VERSION, MONGO_INITDB_ROOT_USERNAME, MONGO_INITDB_ROOT_PASSWORD, MONGODB_UI_PORT (if UI enabled), MONGO_EXPRESS_BASICAUTH (if UI enabled: true if auth, false if not), MONGO_EXPRESS_USER (if UI auth), MONGO_EXPRESS_PASSWORD (if UI auth) |
For alias install — prefix all env var names with ENV_PREFIX_:
e.g. REDIS_CACHE_PORT, REDIS_CACHE_VERSION, REDIS_CACHE_PASSWORD, etc.
Conflict detection: Before appending each key, check if the key already exists in
.devtools/${SERVICE_SLUG}/.env. If it does:
## REPLACED as an inline comment on that line.Why per-service subfolder, not root .devtools/.env? (D-05/D-06)
Credentials belong to their service. Deleting .devtools/redis/ removes Redis completely —
including its credentials — without touching other services. The root .devtools/.env stays
minimal (project-level vars only), making it safe to inspect without risk of leaking service creds.
The root .devtools/.env holds ONLY project-level vars that Docker Compose needs when
running the root compose.yml. Write these on first install only (skip if already present):
COMPOSE_PROJECT_NAME=<value>
COMPOSE_PROFILES=services
COMPOSE_PROJECT_NAME is not yet in .devtools/.env, prepend it as the very first entry.COMPOSE_PROFILES is not yet in .devtools/.env, add it.COMPOSE_PROFILES=services ensures docker compose up starts core service containers.
Without it the default profile is empty and all containers are skipped.COMPOSE_PROFILES entry — the user may have added ui
or monitoring to it..devtools/.env.The root .devtools/.env.example documents the project-level structure only. Update it on
first install (skip if COMPOSE_PROJECT_NAME is already present):
# .devtools/.env — project-level vars (safe to inspect; do NOT commit per-service .env files)
COMPOSE_PROJECT_NAME=myapp
COMPOSE_PROFILES=services
# Per-service credentials live in their service subfolders:
# .devtools/redis/.env — Redis credentials
# .devtools/postgres/.env — PostgreSQL credentials
# .devtools/rabbitmq/.env — RabbitMQ credentials
# .devtools/mysql/.env — MySQL credentials
# .devtools/mongodb/.env — MongoDB credentials
# All subfolder .env files are gitignored via **/.env in .devtools/.gitignore
Do NOT append per-service credential keys to .devtools/.env.example. Per-service vars are
self-documenting in .devtools/${SERVICE_SLUG}/.env which the skill creates.
Write monitoring credentials to .devtools/monitoring/.env
(the mkdir -p .devtools/monitoring will be run in Step 10d before writing these):
# ── Monitoring ──────────────────────────────────────
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=<ANSWERS[grafana_password]>
Apply conflict detection for GRAFANA_ADMIN_USER and GRAFANA_ADMIN_PASSWORD within
.devtools/monitoring/.env. Do NOT write monitoring vars to root .devtools/.env.
Copy monitoring templates verbatim (no token substitution needed):
Create monitoring subfolder and write the compose template:
mkdir -p .devtools/monitoring
curl -fsSL "${SKILL_RAW_BASE}/compose-templates/monitoring/monitoring.compose.yml"
Write fetched content to .devtools/monitoring/monitoring.compose.yml.
Then write .devtools/monitoring/.env as specified in Step 10c.
If ANSWERS[taskfile]=true, fetch and write the Taskfile:
curl -fsSL "${SKILL_RAW_BASE}/taskfile-templates/monitoring/monitoring.Taskfile.yml"
Write fetched content to .devtools/monitoring/monitoring.Taskfile.yml.
After writing monitoring/monitoring.compose.yml (step 1 above), update .devtools/compose.yml
to add the monitoring include (using the same create-or-append logic as Step 11a):
- monitoring/monitoring.compose.yml
Important: Write monitoring/monitoring.compose.yml before updating compose.yml include —
same write-order rule as Step 9.
Check if .devtools/compose.yml exists:
If it does NOT exist (first service install):
Create .devtools/compose.yml with this content:
# .devtools/compose.yml
# Root compose aggregator — managed by dev-tools skill
# Do not edit manually; run the skill to add or remove services.