Local bootstrap entrypoint for the Harness Landing Zone chicken-and-egg setup.
This folder is used before the shared Harness pipeline can manage the platform for itself. It creates the first organization and projects, seeds the bootstrap Git connector and secrets, and creates the service account and token used by the pipeline that will take over later.
Use this entrypoint to stand up the initial platform-management footprint:
- The first Harness organization (by default,
Harness Platform Accelerator) - Its starter projects (
Platform Management) - Org-scoped resources from the embedded
organizations/config tree - A deploy service account, named after the org, with its API key, token, and a secret that holds the token
- The deploy pipelines, imported from the
.harness/folder
This entrypoint is meant to be run locally. It is not the long-term deployment path.
You need:
- OpenTofu installed (the
tofucommand) - A Harness account and a Platform API key with account-admin rights
- A GitHub App that can read your config repo (only if you keep the Git connector)
Some values are specific to your setup. Set these before you apply:
organization_name, and the matching folder name underorganizations/harness_platform_account(your Harness account ID)- in
organizations/<org>/git-connectors/platform_configs.yaml: theconnector_url(your GitHub org, not the example) and the GitHub Appapplication_id/installation_id
This folder is a thin wrapper around the shared module in ../modules/harness-resources.
- main.tf points the module at this folder's own
organizations/tree, so the bootstrap is self-contained and does not need an external config repo. - harness_service_accounts.tf creates the deploy service account and stores its token as a secret named after the org (for the default org,
harness_platform_accelerator_platform_deployer_token). The deploy pipelines read this secret to log in to Harness. - The pipeline definitions live in .harness/ in this folder. Each project's
pipelines/*.yamlfiles are small pointers that tell Harness which.harness/file to import.
There is no providers.tf in this folder, so the harness/harness provider is
configured entirely from environment variables. Before running any tofu
command you must export, at minimum, your account ID and a Platform (NextGen)
API key:
export HARNESS_ACCOUNT_ID="<your-account-id>"
export HARNESS_PLATFORM_API_KEY="pat.<your-account-id>.xxxxxxxx.yyyyyyyy"
# Optional — only for self-hosted / custom-domain instances:
# export HARNESS_ENDPOINT="https://app.harness.io/gateway"- Use a Platform API key (
HARNESS_PLATFORM_API_KEY), not a FirstGenHARNESS_API_KEY— these resources are NextGen. - The key needs account-admin level rights, because the bootstrap creates a service account and an account-admin role assignment.
- Without these,
tofu planfails fast withaccount_id is not setand401 Unauthorizedon theharness_platform_permissionslookup.
var.harness_platform_account (used to stamp account_id on resources) is
separate from the provider's HARNESS_ACCOUNT_ID; set both to the same value.
This entrypoint defaults to local state — the backend "gcs" {} block in
terraform.tf is commented out, so no GCS bucket or GCP
credentials are required to run it locally. The gcs_bucket / gcp_* variables
are optional and consumed only by the deploy pipeline (for tofu init -backend-config and OIDC), never by a resource in this module.
cd harness-platform-bootstrap
tofu init # local state, no backend config neededTo use a remote GCS backend instead, uncomment the backend "gcs" {} block and
init with the bucket/prefix:
tofu init \
-backend-config="bucket=<gcs-bucket>" \
-backend-config="prefix=<state-prefix>"Minimum required inputs:
harness_platform_account— your Harness account ID (supply per environment viaterraform.tfvarsorHARNESS_ACCOUNT_ID)organization_name— the org config-folder name underorganizations/(defaults toharness-platform-accelerator). This selects which config tree to deploy; the org display name and identifier come from that folder'sconfig.yaml, falling back to this value only whenconfig.yamlomits them.
Optional inputs:
harness_platform_urltagsgit_connector_credentials— required if a git-connector YAML declaresapi_auth/github_appwithout inline credentials (see Current Caveats).
Expected local-only artifacts:
terraform.tfvars(gitignored) for any overridespem-folder/contents for file-type secrets
Credentials should come from environment variables or gitignored local files, never committed config.
The platform_configs GitHub connector uses GitHub App auth. Harness
requires every GitHub connector to declare credentials (http or ssh), so the
App coordinates must be supplied one of two ways. Neither exposes secret
material: application_id and installation_id are non-secret identifiers,
and private_key_ref is a reference to the PEM secret (created from
secrets/github_app_key.yaml, identifier harness_lz_key), never the key itself.
Way 1 — inline in the connector YAML (git-connectors/platform_configs.yaml):
http_credentials:
github_app:
application_id: "123456"
installation_id: "12345678"
private_key_ref: org.harness_lz_key
api_auth:
github_app_api: trueWay 2 — via the git_connector_credentials variable, keyed by connector
identifier. Omit the http_credentials block from the YAML and supply it either
as an env var:
export TF_VAR_git_connector_credentials='{"platform_configs":{"http_credentials":{"github_app":{"application_id":"123456","installation_id":"12345678","private_key_ref":"org.harness_lz_key"}},"api_auth":{"github_app_api":true}}}'or in a gitignored terraform.tfvars:
git_connector_credentials = {
platform_configs = {
http_credentials = {
github_app = {
application_id = "123456"
installation_id = "12345678"
private_key_ref = "org.harness_lz_key"
}
}
api_auth = { github_app_api = true }
}
}Resolution order: inline YAML wins; the variable is the fallback
(try(cnf.http_credentials, var.git_connector_credentials[<id>].http_credentials, null)).
Use Way 1 for shared, non-secret coordinates checked into the repo; use
Way 2 when injecting at runtime — the bootstrap_deploy pipeline builds this
exact JSON from its github_app_* pipeline variables.
This entrypoint reads config from its own local tree:
harness-platform-bootstrap/
├── .harness/ # pipeline definitions, imported into Harness
└── organizations/
└── harness-platform-accelerator/ # folder name == organization_name
├── config.yaml # org name + identifier
├── git-connectors/
├── secrets/
└── projects/
├── Infrastructure/
└── Platform Management/
├── config.yaml
└── pipelines/ # pointers to the .harness/ files above
That makes this folder self-contained for the initial bootstrap run.
- Run this folder locally to create the first org, first projects, bootstrap connector, bootstrap secrets, and the deployer service account.
- Use the bootstrap pipeline in Harness to provision additional environments.
- Migrate the pipeline to use harness-platform-deployment as the root consumer once that flow is validated.
- Use the reusable modules behind that root:
harness-platform-setup,harness-organization, andharness-project.
Two pipeline execution modes are planned for the steady state:
- Git-driven
- Harness workflow-driven
From this directory:
# 1. Authenticate (see Authentication section)
export HARNESS_ACCOUNT_ID="<your-account-id>"
export HARNESS_PLATFORM_API_KEY="pat.<your-account-id>.xxxx.yyyy"
# 2. Init with local state (no backend config needed)
tofu init
# 3. Validate and review
tofu validate
tofu planDo not run tofu apply unless explicitly intended for the bootstrap step.