Kinsta support
@retlehs is already working on this.
Since Sep 9, 2025.
- Dominant language
- Go
- Stars
- 169
- Forks
- 30
- Avg merge
- 13h 43m
- Merged PRs (30d)
- 7
Description
Background
Trellis deployments to Kinsta require significant manual configuration documented in Kinsta's Bedrock/Trellis guide. Two recent Kinsta developments make automation practical:
-
Custom webroot API (changelog) — programmatically set webroot to
/current/webviaPOST /v2/sites/environments/{env_id}/change-webroot-subfolder, eliminating the old support-ticket workflow. -
Site Environments API (docs) — retrieve environment details (SSH host/port, paths), create/clone environments, manage SFTP access. Note: DB credentials are not available via the API — they must be entered manually or sourced from MyKinsta.
Scope
v1 does:
trellis kinsta setupcommand that configures a single Trellis environment for Kinsta deployment- Kinsta API client for site/environment discovery, SFTP config, and webroot changes
- Host inventory generation, group_vars configuration, deploy hook patching
- Bedrock config updates (DB defines, CDN/mu-plugin constants)
- kinsta-mu-plugins installation via Composer
- Webroot configuration via API
- Persisted site/company mappings in
.trellis/kinsta.yml
v1 does not:
- Write DB credentials automatically (API doesn't expose them; user runs
trellis vault edit) - Manage DNS or SSL
- Provide a custom deploy command (standard
trellis deployworks after setup) - Modify
cli_config.Configstruct ortrellis.cli.yml(feature-local config only)
Command
trellis kinsta setup [--company=<id>] [--environment=<environment>] [--site=<site>] [--kinsta-site=<id>] [--force]
Flags:
--company— Kinsta company ID (also viaKINSTA_COMPANY_IDenv var orcompanyin.trellis/kinsta.yml). Prompted interactively if not provided.--environment— Trellis environment name (stagingorproduction).--site— Trellis site key as defined inwordpress_sites.yml(e.g.example.com). This is the local Trellis site name, not the Kinsta site ID.--kinsta-site— explicit Kinsta site ID, bypassing name matching entirely. Useful when Trellis and Kinsta naming conventions differ.--force— overwrite existing hosts file even if it contains non-Kinsta content.
Flow
-
Authenticate — resolve Kinsta API token from
KINSTA_API_TOKENenv var or prompt.
Resolve company ID using this precedence:--companyflag (highest)KINSTA_COMPANY_IDenv varcompanyin.trellis/kinsta.yml- Interactive prompt (lowest)
-
Select Kinsta site/environment — map Trellis
--environmentto Kinsta environment type:productionmaps tolive,stagingmaps tostaging, anything else errors with a message listing valid values. Then resolve Kinsta site ID using this precedence:--kinsta-siteflag (highest)- Persisted mapping in
.trellis/kinsta.yml(sites.<site>.<env>) --sitename matched against Kinsta site names via API- Interactive selection from API site list (lowest)
For name matching (3): zero matches errors, multiple matches prompts. On first successful mapping, offer to persist the Kinsta site ID to
.trellis/kinsta.ymlfor future runs. Persistence requires both--site(or single-site project auto-detect) and--environmentto be known — if either is missing, the mapping is not persisted (the resolved ID is still used for the current run). -
Fetch environment details — call
ListEnvironmentsfor environment metadata,
thenGetSFTPConfigfor SSH host, port, and username. -
Generate host inventory — write/update
hosts/<env>:kinsta_<env> ansible_host=<ip> ansible_ssh_port=<port> ansible_ssh_extra_args='-o StrictHostKeyChecking=no' [web] kinsta_<env> [<env>] kinsta_<env> -
Update
group_vars/<env>/main.yml— create if needed, append Kinsta-specific vars (this file isn't standard in all Trellis projects but is the correct place for environment-level overrides that aren't site-specific):project_root: /www/<site_path>/public www_root: /www/<site_path>/public web_user: <kinsta_user> web_group: www-data -
Update
ansible.cfg— setforks = 3(host key checking is scoped to the Kinsta host via inventory vars in step 4, not set globally) -
Patch deploy hooks — modify
roles/deploy/hooks/finalize-after.yml:- Remove
Reload php-fpmtask (Kinsta manages PHP) - Add
Clear Kinsta cachetask via URI module
- Remove
-
Update Bedrock config (
config/application.php):- Add DB credential defines for MyKinsta compatibility
- Add
KINSTA_CDN_USERDIRSandKINSTAMU_CUSTOM_MUPLUGIN_URLconstants
-
Add kinsta-mu-plugins via
retlehs/kinsta-mu-plugins:- Add VCS repository entry to
composer.jsonrepositories. Detect whetherrepositoriesis an array (common) or object (named keys) and merge accordingly:
Deduplicate by URL to ensure idempotency.{ "type": "vcs", "url": "https://github.com/retlehs/kinsta-mu-plugins.git" } - Check if
kinsta/kinsta-mu-pluginsalready exists inrequirewith a compatible constraint — if so, skipcomposer require. Otherwise runcomposer require kinsta/kinsta-mu-plugins:^2to pin to current major (this handles adding therequireentry and updating the lock file)
- Add VCS repository entry to
-
Set webroot via API —
POST /v2/sites/environments/{env_id}/change-webroot-subfolderwithweb_root_subfolder: "/current/web"(leading slash per API convention) -
Print next steps — direct user to MyKinsta for DB credentials and to run
trellis vault edit <environment>to configure them.
Each step checks current state before modifying. Re-running on an already-configured environment is safe (update values if changed, skip if current).
Config: .trellis/kinsta.yml
company: "company-id"
sites:
example.com: # Trellis site key
staging: "kinsta-site-id"
production: "kinsta-site-id"
- Created on first
trellis kinsta setupwhen user opts to persist. - Git-tracked by default — contains non-sensitive IDs/mappings useful to the whole team. No API tokens are stored here (those stay in env vars). Teams that prefer local-only can add it to
.gitignore; the setup command does not manage.gitignoreentries for this file. - Malformed file: parse error aborts with message showing the file path and YAML error. Never silently ignore or overwrite a malformed file.
- Loaded by
kinstacommands only. Feature-local file avoids modifying the sharedcli_config.Configstruct and its validators.
Implementation
Additive changes only — minimal edits to existing files (main.go command registration).
All Kinsta logic lives in new files.
New files
pkg/kinsta/
client.go # API client — auth, HTTP, error handling
types.go # API request/response types (Site, Environment, etc.)
client_test.go
cmd/
kinsta.go # Namespace command
kinsta_setup.go # Setup command implementation
kinsta_setup_test.go
Kinsta API client
type Client struct {
Token string
BaseURL string // https://api.kinsta.com/v2
HTTP *http.Client
}
func (c *Client) ListSites(ctx context.Context, company string) ([]Site, error) // GET /sites?company=<id>
func (c *Client) ListEnvironments(ctx context.Context, siteID string) ([]Environment, error) // GET /sites/{site_id}/environments
func (c *Client) GetSFTPConfig(ctx context.Context, siteID, envID string) (*SFTPConfig, error) // GET /sites/{site_id}/environments/{env_id}/ssh/config
func (c *Client) ChangeWebroot(ctx context.Context, envID string, subfolder string) error // POST /sites/environments/{env_id}/change-webroot-subfolder
Command registration (main.go)
"kinsta": func() (cli.Command, error) {
return &cmd.NamespaceCommand{
HelpText: "Usage: trellis kinsta <subcommand> [<args>]",
SynopsisText: "Commands for Kinsta hosting integration",
}, nil
},
"kinsta setup": func() (cli.Command, error) {
return cmd.NewKinstaSetupCommand(ui, trellis), nil
},
File mutation strategy
Managed blocks — for file types that support comments (hosts/INI, YAML, PHP), wrap auto-generated sections with markers (# BEGIN trellis-kinsta / # END trellis-kinsta, or // BEGIN trellis-kinsta for PHP) so that reruns can find and update existing blocks deterministically.
JSON files (composer.json) cannot carry comments — use structural merge only.
Parse strategies:
- Hosts (INI) — template overwrites file. If it already exists with non-Kinsta content, require
--forceto overwrite (default: abort with error). Back up original tohosts/<env>.bakbefore overwriting. - YAML (
group_vars) — merge keys rather than append blindly. - PHP (
application.php) — string match for existing defines before inserting. Use// BEGIN/END trellis-kinstamarkers. - JSON (
composer.json) — detectrepositoriesshape (array or object), append/merge VCS entry, deduplicate by URL. Thencomposer requireowns therequirekey and lock. - YAML (deploy hooks) — parse task list, remove/add by task
namefield.
No rollback — if a step fails mid-way, leave partial changes in the working tree. User can review with git diff and restore selectively or via VCS as needed.
PR Execution Plan
Single PR, built and reviewed piece by piece:
-
pkg/kinsta/— API client + types + tests (mockable HTTP) -
cmd/kinsta.go— namespace command -
cmd/kinsta_setup.go— command skeleton + flag parsing + config loading - Site/company resolution logic with precedence rules
-
.trellis/kinsta.ymlread/write/persist - File mutations: hosts, group_vars, ansible.cfg, deploy hooks
- File mutations: application.php, composer.json
- Webroot API call + next-steps output
-
main.go— command registration - Tests: golden files, precedence, integration, edge cases
Acceptance Criteria
-
trellis kinsta setupconfigures all automatable Kinsta integration steps end-to-end (DB creds remain manual viatrellis vault edit) - Idempotent: rerunning on an already-configured environment produces no unnecessary changes
- Company precedence honored:
--company>KINSTA_COMPANY_IDenv var >.trellis/kinsta.yml> prompt - Site precedence honored:
--kinsta-site>.trellis/kinsta.ymlmapping >--sitename match > interactive - No global
host_key_checking = False— scoped to Kinsta host via inventory vars only - Existing hosts file with non-Kinsta content is not overwritten without
--force - Malformed
.trellis/kinsta.ymlaborts with clear parse error - DB credentials: user directed to MyKinsta +
trellis vault edit, not auto-written -
composer requireskipped when package already present with compatible constraint -
composer.jsonrepositories array/object shape detected and handled correctly -
--forcecreateshosts/<env>.bakbefore overwriting existing hosts file
Test Matrix
- API client — mock HTTP responses for each endpoint, error cases
- Golden files — hosts, group_vars, deploy hooks, composer.json, application.php (fresh setup + idempotent rerun)
- Precedence — company (
--company> env var > config > prompt), site (--kinsta-site> config mapping >--sitename match > interactive) - Environment mapping —
production->live,staging->staging, invalid value -> error with valid options listed - Edge cases:
-
--sitewith zero Kinsta matches (error) -
--sitewith multiple matches (prompt) - Existing non-Kinsta hosts file without
--force(abort) - Existing non-Kinsta hosts file with
--force(backup + overwrite) - Pre-existing Kinsta blocks in files (rerun/update)
- Missing optional files (e.g. no
finalize-after.yml) - Composer require skipped when already present
- Malformed
.trellis/kinsta.yml(abort with parse error)
-
Future Commands
Once the foundation exists, these become straightforward additions:
trellis kinsta cache clear— clear Kinsta cache via APItrellis kinsta environments— list environments for a sitetrellis kinsta deploy— deploy wrapper with Kinsta-specific pre/post hooks
Ref https://kinsta.com/blog/bedrock-trellis/
Ref https://kinsta.com/changelog/custom-webroot/
Ref https://api-docs.kinsta.com/tag/WordPress-Site-Environments
Ref https://api-docs.kinsta.com/api-reference/wordpress-sites/get-list-of-company-sites
Ref https://api-docs.kinsta.com/api-reference/wordpress-site-environments/get-sftpssh-connections-config
Ref https://api-docs.kinsta.com/api-reference/wordpress-site-environments/change-webroot-subfolder-of-environment
Ref https://api-docs.kinsta.com/api-reference/wordpress-site-environments/get-site-environments
Ref https://github.com/retlehs/kinsta-mu-plugins
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.