12factor-rock
Installation
SKILL.md
12factor Rock
Stay inside the extension. If the repo only works after replacing the extension, stop.
Skill Order
- Run
$12factor-fitfirst when possible. - Run this skill before
$12factor-juju-terraform. - This skill and
$12factor-charmcan proceed independently once the fit verdict, framework, and target context are clear.
Workflow
- Reuse the fit verdict from
$12factor-fitif available. If not, inspect the repo and confirm the framework yourself. - Run
scripts/check_rock_contract.py <repo> --framework <framework>before generating anything. - Load
references/framework-rock-contracts.mdfor the exact project contract of the chosen framework. - If the framework is FastAPI, Go, ExpressJS, or Spring Boot, verify the user
accepted the experimental extension path and that
rockcraftis on an edge channel withROCKCRAFT_ENABLE_EXPERIMENTAL_EXTENSIONS=true. - If the repo clearly separates frontend and backend in a monorepo, confirm
the backend subdirectory is the target scope before running
rockcraft init. Do not default to one combined image for that repo shape. - If the repo has a frontend or static-asset build step, ask the user explicitly whether it should run inside this rock build or remain a separate deployment concern. Do not decide that on your own.
- If the project does require a frontend build inside the same rock, prefer a Rockcraft plugin-backed build part when the frontend is a distinct build unit. Only use shell steps inside an extension-owned part when the frontend is already part of the same runtime application surface.
- If the app needs database migrations, prefer a root
migrate.shfor Flask, FastAPI, ExpressJS, and Go when the repo does not already provide a supported migration entrypoint. For Django, remember currentpaas-charmcode usesmanage.py migratewhenevermanage.pyexists, so a newmigrate.shwill not replace that path. For Spring Boot, prefer framework-managed migrations unless the repo already exposes a compatible wrapper. - If the app needs extra background services, add
rockcraft.yamlservices whose names end in-workeror-scheduler, using repo-backed or user-confirmed commands only. - Run the exact profile from
references/framework-rock-contracts.md, for examplerockcraft init --profile fastapi-framework. Always use this command to generaterockcraft.yaml— never copy from templates, previously generated files, or example rocks. - Inspect the generated
rockcraft.yaml. - Run
rockcraft expand-extensionsbefore making non-trivial edits. - If you add or override Pebble services, inspect the effective expanded service command, user, and working directory before packing.
- If the service runs as a non-root user such as
_daemon_, verify any file or directory the app may create, rewrite, or persist at startup is writable by that runtime user, or relocate that mutable state to a more appropriate writable path. - If the application binary is a multi-command CLI, make the Pebble service
command invoke the actual long-running subcommand (
server,worker, etc.) instead of the bare top-level CLI. - Keep edits inside extension-owned parts and the minimal metadata fields described in
references/allowed-edits.md. - Build with
rockcraft pack. Do not use--destructive-mode; if the normal Rockcraft build path is blocked, stop and ask before bypassing confinement. - If
rockcraft packfails onbase: bare, first try a supported Ubuntu base fromreferences/framework-rock-contracts.mdbefore making deeper dependency changes. Only move into dependency surgery after that base trial fails or is not allowed for the framework. - Push the result to the user-selected registry only after the target registry has been preflighted, using Rockcraft-shipped tooling when available.
Allowed Changes
- set name, summary, description, version, and platforms
- add build or stage packages the app truly needs
- add required staged files the extension does not already include
- tune extension-owned parts
- add a root
migrate.shwhen the app needs database migrations and the repo does not already expose a supported entrypoint - add
rockcraft.yamlservices ending in-workeror-schedulerwhen the app needs background services alongside the main web service - add a small repo-backed startup wrapper or entrypoint shim in the workload
image when the app needs a minimal bridge from fixed
paas-charmenv names to its existing runtime contract - adjust ownership or mode of runtime-mutated files or directories inside extension-owned parts when the workload runs as a non-root user and needs write access at startup
- run frontend builds or static-asset preparation steps in the rock build when the application requires them and the user explicitly confirmed that choice
- add a dedicated plugin-backed frontend build part when the frontend is a distinct build unit inside the same application scope after the user explicitly confirmed that same-image path
- when
base: barebuild fails, try a supported Ubuntu base before deeper dependency changes - move
build-baseto an older supported Ubuntu release whenbase: bareallows it and the failure is interpreter compatibility
Disallowed Changes
- do not rewrite the rock from
expand-extensionsoutput - do not create a bespoke manual rock as a silent fallback
- do not generate
rockcraft.yamlby copying from templates, previously generated files, or example rocks — always userockcraft init --profile <framework> - do not default to bundling clearly separated frontend and backend monorepos into one image
- do not widen the staged file set unless the extension contract actually requires it
- do not choose embedded frontend/static builds without explicit user confirmation
- do not upgrade app dependencies before trying an older supported
build-basefor Python framework rocks - do not jump straight into dependency upgrades or deep dependency edits on a
base: barefailure before trying a supported Ubuntu base - do not invent worker or scheduler commands without repo evidence or a user-confirmed handoff
- do not add a duplicate migration path when the framework already manages it and the intended repo behavior is clear
- do not move static-asset or frontend build work into the charm when it can be done at rock build time
- do not reach for external container tooling to move the rock if Rockcraft already ships the required OCI tooling
- do not use
rockcraft pack --destructive-modeas a fallback; stop and ask if the standard build path is not viable
Framework Rules
Use references/framework-rock-contracts.md every time. The important differences are:
- Flask and Django are Gunicorn-based
- FastAPI is Uvicorn-based
- ExpressJS requires
app/package.json - Go is binary-first and uses separate assets staging
- Spring Boot requires one active build system only
-workerand-schedulerservice names control how 12-factor charms run extra services
Output Contract
Produce:
- a minimal
rockcraft.yaml - a clear list of any repo or layout adaptations that were required
- the built rock reference ready for registry push
- the exact rock push command, preferring Rockcraft-shipped
skopeowhen OCI copy is required, including--insecure-policywhen the host has no containers policy file - an explicit note if the repo does not fit the supported extension path