[{"data":1,"prerenderedAt":1278},["ShallowReactive",2],{"navigation":3,"\u002Finstallation\u002Frunner":194,"\u002Finstallation\u002Frunner-surround":1273},[4,18,31,64,81,102,111,132,149],{"title":5,"path":6,"stem":7,"children":8,"page":17},"Getting started","\u002Fgetting-started","1.getting-started",[9,13],{"title":10,"path":11,"stem":12},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction",{"title":14,"path":15,"stem":16},"Run your first workflow","\u002Fgetting-started\u002Fquickstart","1.getting-started\u002F2.quickstart",false,{"title":19,"path":20,"stem":21,"children":22,"page":17},"Core concepts","\u002Fconcepts","2.concepts",[23,27],{"title":24,"path":25,"stem":26},"The platform model","\u002Fconcepts\u002Fplatform","2.concepts\u002F1.platform",{"title":28,"path":29,"stem":30},"Architecture and boundaries","\u002Fconcepts\u002Farchitecture","2.concepts\u002F2.architecture",{"title":32,"path":33,"stem":34,"children":35,"page":17},"Installation","\u002Finstallation","3.installation",[36,40,44,48,52,56,60],{"title":37,"path":38,"stem":39},"Overview and requirements","\u002Finstallation\u002Foverview","3.installation\u002F1.overview",{"title":41,"path":42,"stem":43},"Local evaluation","\u002Finstallation\u002Flocal-evaluation","3.installation\u002F2.local-evaluation",{"title":45,"path":46,"stem":47},"Install the server","\u002Finstallation\u002Fserver","3.installation\u002F3.server",{"title":49,"path":50,"stem":51},"Install a runner","\u002Finstallation\u002Frunner","3.installation\u002F4.runner",{"title":53,"path":54,"stem":55},"Verify the installation","\u002Finstallation\u002Fverification","3.installation\u002F5.verification",{"title":57,"path":58,"stem":59},"Upgrade the platform","\u002Finstallation\u002Fupgrades","3.installation\u002F6.upgrades",{"title":61,"path":62,"stem":63},"Guided installer","\u002Finstallation\u002Finstaller","3.installation\u002F7.installer",{"title":65,"path":66,"stem":67,"children":68,"page":17},"Build workflows","\u002Fworkflows","4.workflows",[69,73,77],{"title":70,"path":71,"stem":72},"Build a Definition","\u002Fworkflows\u002Fdefinitions","4.workflows\u002F0.definitions",{"title":74,"path":75,"stem":76},"Deploy and run workflows","\u002Fworkflows\u002Fruns","4.workflows\u002F1.runs",{"title":78,"path":79,"stem":80},"Inputs, context, and secrets","\u002Fworkflows\u002Finputs-and-secrets","4.workflows\u002F2.inputs-and-secrets",{"title":82,"path":83,"stem":84,"children":85,"page":17},"Run and recover","\u002Foperations","5.operations",[86,90,94,98],{"title":87,"path":88,"stem":89},"Monitor Runs","\u002Foperations\u002Fmonitoring","5.operations\u002F1.monitoring",{"title":91,"path":92,"stem":93},"Approve prepared steps","\u002Foperations\u002Fapprovals","5.operations\u002F2.approvals",{"title":95,"path":96,"stem":97},"Recover or cancel execution","\u002Foperations\u002Frecovery","5.operations\u002F3.recovery",{"title":99,"path":100,"stem":101},"Troubleshooting","\u002Foperations\u002Ftroubleshooting","5.operations\u002F4.troubleshooting",{"title":103,"path":104,"stem":105,"children":106,"page":17},"Action catalog","\u002Fcatalog","6.catalog",[107],{"title":108,"path":109,"stem":110},"Find and use Actions","\u002Fcatalog\u002Foverview","6.catalog\u002F1.overview",{"title":112,"path":113,"stem":114,"children":115,"page":17},"Administration","\u002Fadministration","7.administration",[116,120,124,128],{"title":117,"path":118,"stem":119},"Registries and Environments","\u002Fadministration\u002Fenvironments","7.administration\u002F2.environments",{"title":121,"path":122,"stem":123},"Manage runners","\u002Fadministration\u002Frunners","7.administration\u002F3.runners",{"title":125,"path":126,"stem":127},"Access and approval policies","\u002Fadministration\u002Faccess","7.administration\u002F4.access",{"title":129,"path":130,"stem":131},"Backups and upgrades","\u002Fadministration\u002Fmaintenance","7.administration\u002F5.maintenance",{"title":133,"path":134,"stem":135,"children":136,"page":17},"Develop Actions","\u002Factions","8.actions",[137,141,145],{"title":138,"path":139,"stem":140},"Build your first Action","\u002Factions\u002Fquickstart","8.actions\u002F1.quickstart",{"title":142,"path":143,"stem":144},"Local development","\u002Factions\u002Flocal-development","8.actions\u002F2.local-development",{"title":146,"path":147,"stem":148},"Publish an Action","\u002Factions\u002Fpublishing","8.actions\u002F3.publishing",{"title":150,"path":151,"stem":152,"children":153,"page":17},"Reference","\u002Freference","9.reference",[154,158,162,166,170,174,178,182,186,190],{"title":155,"path":156,"stem":157},"Operator CLI","\u002Freference\u002Foperator-cli","9.reference\u002F1.operator-cli",{"title":159,"path":160,"stem":161},"Installer CLI","\u002Freference\u002Finstaller-cli","9.reference\u002F10.installer-cli",{"title":163,"path":164,"stem":165},"Definition syntax","\u002Freference\u002Fdefinition","9.reference\u002F2.definition",{"title":167,"path":168,"stem":169},"CLI configuration","\u002Freference\u002Fconfiguration","9.reference\u002F3.configuration",{"title":171,"path":172,"stem":173},"API integration","\u002Freference\u002Fapi","9.reference\u002F4.api",{"title":175,"path":176,"stem":177},"Action CLI","\u002Freference\u002Faction-cli","9.reference\u002F5.action-cli",{"title":179,"path":180,"stem":181},"Action SDK","\u002Freference\u002Fsdk","9.reference\u002F6.sdk",{"title":183,"path":184,"stem":185},"Contracts and compatibility","\u002Freference\u002Fcontracts","9.reference\u002F7.contracts",{"title":187,"path":188,"stem":189},"Server configuration","\u002Freference\u002Fserver-configuration","9.reference\u002F8.server-configuration",{"title":191,"path":192,"stem":193},"Runner configuration","\u002Freference\u002Frunner-configuration","9.reference\u002F9.runner-configuration",{"id":195,"title":49,"body":196,"description":1268,"extension":1269,"meta":1270,"navigation":926,"path":50,"seo":1271,"stem":51,"__hash__":1272},"docs\u002F3.installation\u002F4.runner.md",{"type":197,"value":198,"toc":1261},"minimark",[199,213,228,233,236,276,287,291,299,319,345,348,645,666,669,709,716,722,726,733,740,789,796,799,844,855,861,1131,1141,1166,1170,1173,1192,1211,1218,1221,1225,1236,1246,1257],[200,201,202,203,207,208,212],"p",{},"Start with a working ",[204,205,206],"a",{"href":46},"server"," and an administrator login. Use the ",[209,210,211],"strong",{},"same platform version"," as the server. A runner executes Actions through its local Docker daemon and connects outbound to the server's HTTPS and advertised NATS TLS endpoints.",[200,214,215,216,220,221,224,225,227],{},"Choose one deployment method below. The examples create ",[217,218,219],"code",{},"runner-01"," in the initial ",[217,222,223],{},"local"," pool. Additional runners need their own identity, enrollment, and persistent volume. Pools and ongoing admission management are covered in ",[204,226,121],{"href":122},".",[229,230,232],"h2",{"id":231},"create-an-enrollment-credential","Create an enrollment credential",[200,234,235],{},"On the administrator workstation, save the response privately and extract the token without printing it:",[237,238,241],"code-example",{":line-numbers":239,"caption":240,":wrap":239},"true","Create a private, single-use enrollment credential before registering the runner.",[242,243,253],"pre",{"className":244,"code":245,"filename":246,"highlights":247,"language":251,"meta":252,"style":252},"language-sh shiki shiki-themes pyplines-light pyplines-light pyplines-dark","umask 077\npyplines --json runner enroll runner-01 --pool local --ttl 3600 > enrollment.json\npython3 -c 'import json; print(json.load(open(\"enrollment.json\"))[\"token\"])' > enrollment-token\n","Terminal",[248,249,250],1,2,3,"sh","",[217,254,255,264,270],{"__ignoreMap":252},[256,257,261],"span",{"class":258,"line":248},[259,260],"line","highlight",[256,262,263],{},"umask 077\n",[256,265,267],{"class":266,"line":249},[259,260],[256,268,269],{},"pyplines --json runner enroll runner-01 --pool local --ttl 3600 > enrollment.json\n",[256,271,273],{"class":272,"line":250},[259,260],[256,274,275],{},"python3 -c 'import json; print(json.load(open(\"enrollment.json\"))[\"token\"])' > enrollment-token\n",[200,277,278,279,282,283,286],{},"Securely transfer ",[217,280,281],{},"enrollment-token"," and the server's public ",[217,284,285],{},"ca.crt"," to the runner host. This enrollment expires after one hour, is single-use, and requires administrator approval. Create a fresh enrollment if it expires before registration. Do not transfer the server private key.",[229,288,290],{"id":289},"option-a-docker-compose","Option A: Docker Compose",[200,292,293,294,298],{},"The ",[204,295,297],{"href":296},"\u002Finstallation\u002Finstaller#install-a-runner","guided installer"," can prepare these\nresources and register the runner. The instructions below describe manual provisioning.",[200,300,301,302,305,306,309,310,313,314,318],{},"On the runner host, create a private installation directory containing ",[217,303,304],{},"enrollment\u002Ftoken"," and ",[217,307,308],{},"tls\u002Fca.crt",". Copy the enrollment token to that exact filename. Set the plain ",[217,311,312],{},"PLATFORM_VERSION"," selected in ",[204,315,317],{"href":316},"\u002Finstallation\u002Foverview#choose-releases","requirements",":",[242,320,322],{"className":244,"code":321,"filename":246,"language":251,"meta":252,"style":252},"mkdir -p pyplines-runner\u002Fenrollment pyplines-runner\u002Ftls\nchmod 700 pyplines-runner pyplines-runner\u002Fenrollment\ncd pyplines-runner\nprintf 'PLATFORM_VERSION=%s\\n' \"$PLATFORM_VERSION\" > .env\n",[217,323,324,329,334,339],{"__ignoreMap":252},[256,325,326],{"class":259,"line":248},[256,327,328],{},"mkdir -p pyplines-runner\u002Fenrollment pyplines-runner\u002Ftls\n",[256,330,331],{"class":259,"line":249},[256,332,333],{},"chmod 700 pyplines-runner pyplines-runner\u002Fenrollment\n",[256,335,336],{"class":259,"line":250},[256,337,338],{},"cd pyplines-runner\n",[256,340,342],{"class":259,"line":341},4,[256,343,344],{},"printf 'PLATFORM_VERSION=%s\\n' \"$PLATFORM_VERSION\" > .env\n",[200,346,347],{},"Save this file, replacing the server hostname and port with your actual HTTPS endpoint:",[237,349,351],{":line-numbers":239,"caption":350},"PYPLINES_WORK_VOLUME must name the same Docker volume mounted at \u002Fwork.",[242,352,362],{"className":353,"code":354,"filename":355,"highlights":356,"language":361,"meta":252,"style":252},"language-yaml shiki shiki-themes pyplines-light pyplines-light pyplines-dark","name: pyplines-runner-01\nservices:\n  runner:\n    image: ghcr.io\u002Fpyplines\u002Fpyplines-runner:${PLATFORM_VERSION:?Set PLATFORM_VERSION in .env}\n    command:\n      - start\n      - --server\n      - https:\u002F\u002Fpyplines.example.com:8080\n      - --ca-file\n      - \u002Fcerts\u002Fca.crt\n      - --enrollment-token-file\n      - \u002Fenrollment\u002Ftoken\n      - --name\n      - runner-01\n      - --max-concurrent\n      - \"1\"\n    environment:\n      PYPLINES_RUNNER_STATE: \u002Fwork\n      PYPLINES_WORK_VOLUME: pyplines-runner-01-work\n      PYPLINES_ACTION_NETWORK: pyplines-actions\n      DOCKER_HOST: unix:\u002F\u002F\u002Fvar\u002Frun\u002Fdocker.sock\n    volumes:\n      - work:\u002Fwork\n      - \u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n      - .\u002Ftls\u002Fca.crt:\u002Fcerts\u002Fca.crt:ro\n      - .\u002Fenrollment:\u002Fenrollment:ro\n    restart: on-failure\n    stop_grace_period: 60s\nvolumes:\n  work:\n    name: pyplines-runner-01-work\n","compose.yaml",[357,358,359,360],19,20,24,32,"yaml",[217,363,364,377,385,392,402,410,419,427,435,443,451,459,467,475,483,491,505,513,524,535,546,557,565,573,581,589,597,608,619,627,635],{"__ignoreMap":252},[256,365,366,370,373],{"class":259,"line":248},[256,367,369],{"class":368},"smnRP","name",[256,371,318],{"class":372},"sgMYo",[256,374,376],{"class":375},"sfDcV"," pyplines-runner-01\n",[256,378,379,382],{"class":259,"line":249},[256,380,381],{"class":368},"services",[256,383,384],{"class":372},":\n",[256,386,387,390],{"class":259,"line":250},[256,388,389],{"class":368},"  runner",[256,391,384],{"class":372},[256,393,394,397,399],{"class":259,"line":341},[256,395,396],{"class":368},"    image",[256,398,318],{"class":372},[256,400,401],{"class":375}," ghcr.io\u002Fpyplines\u002Fpyplines-runner:${PLATFORM_VERSION:?Set PLATFORM_VERSION in .env}\n",[256,403,405,408],{"class":259,"line":404},5,[256,406,407],{"class":368},"    command",[256,409,384],{"class":372},[256,411,413,416],{"class":259,"line":412},6,[256,414,415],{"class":372},"      -",[256,417,418],{"class":375}," start\n",[256,420,422,424],{"class":259,"line":421},7,[256,423,415],{"class":372},[256,425,426],{"class":375}," --server\n",[256,428,430,432],{"class":259,"line":429},8,[256,431,415],{"class":372},[256,433,434],{"class":375}," https:\u002F\u002Fpyplines.example.com:8080\n",[256,436,438,440],{"class":259,"line":437},9,[256,439,415],{"class":372},[256,441,442],{"class":375}," --ca-file\n",[256,444,446,448],{"class":259,"line":445},10,[256,447,415],{"class":372},[256,449,450],{"class":375}," \u002Fcerts\u002Fca.crt\n",[256,452,454,456],{"class":259,"line":453},11,[256,455,415],{"class":372},[256,457,458],{"class":375}," --enrollment-token-file\n",[256,460,462,464],{"class":259,"line":461},12,[256,463,415],{"class":372},[256,465,466],{"class":375}," \u002Fenrollment\u002Ftoken\n",[256,468,470,472],{"class":259,"line":469},13,[256,471,415],{"class":372},[256,473,474],{"class":375}," --name\n",[256,476,478,480],{"class":259,"line":477},14,[256,479,415],{"class":372},[256,481,482],{"class":375}," runner-01\n",[256,484,486,488],{"class":259,"line":485},15,[256,487,415],{"class":372},[256,489,490],{"class":375}," --max-concurrent\n",[256,492,494,496,499,502],{"class":259,"line":493},16,[256,495,415],{"class":372},[256,497,498],{"class":372}," \"",[256,500,501],{"class":375},"1",[256,503,504],{"class":372},"\"\n",[256,506,508,511],{"class":259,"line":507},17,[256,509,510],{"class":368},"    environment",[256,512,384],{"class":372},[256,514,516,519,521],{"class":259,"line":515},18,[256,517,518],{"class":368},"      PYPLINES_RUNNER_STATE",[256,520,318],{"class":372},[256,522,523],{"class":375}," \u002Fwork\n",[256,525,527,530,532],{"class":526,"line":357},[259,260],[256,528,529],{"class":368},"      PYPLINES_WORK_VOLUME",[256,531,318],{"class":372},[256,533,534],{"class":375}," pyplines-runner-01-work\n",[256,536,538,541,543],{"class":537,"line":358},[259,260],[256,539,540],{"class":368},"      PYPLINES_ACTION_NETWORK",[256,542,318],{"class":372},[256,544,545],{"class":375}," pyplines-actions\n",[256,547,549,552,554],{"class":259,"line":548},21,[256,550,551],{"class":368},"      DOCKER_HOST",[256,553,318],{"class":372},[256,555,556],{"class":375}," unix:\u002F\u002F\u002Fvar\u002Frun\u002Fdocker.sock\n",[256,558,560,563],{"class":259,"line":559},22,[256,561,562],{"class":368},"    volumes",[256,564,384],{"class":372},[256,566,568,570],{"class":259,"line":567},23,[256,569,415],{"class":372},[256,571,572],{"class":375}," work:\u002Fwork\n",[256,574,576,578],{"class":575,"line":359},[259,260],[256,577,415],{"class":372},[256,579,580],{"class":375}," \u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock\n",[256,582,584,586],{"class":259,"line":583},25,[256,585,415],{"class":372},[256,587,588],{"class":375}," .\u002Ftls\u002Fca.crt:\u002Fcerts\u002Fca.crt:ro\n",[256,590,592,594],{"class":259,"line":591},26,[256,593,415],{"class":372},[256,595,596],{"class":375}," .\u002Fenrollment:\u002Fenrollment:ro\n",[256,598,600,603,605],{"class":259,"line":599},27,[256,601,602],{"class":368},"    restart",[256,604,318],{"class":372},[256,606,607],{"class":375}," on-failure\n",[256,609,611,614,616],{"class":259,"line":610},28,[256,612,613],{"class":368},"    stop_grace_period",[256,615,318],{"class":372},[256,617,618],{"class":375}," 60s\n",[256,620,622,625],{"class":259,"line":621},29,[256,623,624],{"class":368},"volumes",[256,626,384],{"class":372},[256,628,630,633],{"class":259,"line":629},30,[256,631,632],{"class":368},"  work",[256,634,384],{"class":372},[256,636,638,641,643],{"class":259,"line":637},31,[256,639,640],{"class":368},"    name",[256,642,318],{"class":372},[256,644,534],{"class":375},[200,646,647,648,651,652,655,656,659,660,662,663,665],{},"The named volume mounted at ",[217,649,650],{},"\u002Fwork"," ",[209,653,654],{},"must be the same volume"," named by ",[217,657,658],{},"PYPLINES_WORK_VOLUME",". The runner writes inputs and reads results through ",[217,661,650],{},"; Docker mounts subdirectories from that named volume into Actions. A separate bind mount at ",[217,664,650],{}," with an unrelated work volume will not work.",[200,667,668],{},"Create the Action network on this Docker host, then start the runner:",[242,670,672],{"className":244,"code":671,"filename":246,"language":251,"meta":252,"style":252},"docker network inspect pyplines-actions >\u002Fdev\u002Fnull 2>&1 || docker network create pyplines-actions\ntest -s enrollment\u002Ftoken && test -s tls\u002Fca.crt\nchmod 600 enrollment\u002Ftoken\ndocker compose config --quiet\ndocker compose pull runner\ndocker compose up -d runner\ndocker compose logs --tail 100 runner\n",[217,673,674,679,684,689,694,699,704],{"__ignoreMap":252},[256,675,676],{"class":259,"line":248},[256,677,678],{},"docker network inspect pyplines-actions >\u002Fdev\u002Fnull 2>&1 || docker network create pyplines-actions\n",[256,680,681],{"class":259,"line":249},[256,682,683],{},"test -s enrollment\u002Ftoken && test -s tls\u002Fca.crt\n",[256,685,686],{"class":259,"line":250},[256,687,688],{},"chmod 600 enrollment\u002Ftoken\n",[256,690,691],{"class":259,"line":341},[256,692,693],{},"docker compose config --quiet\n",[256,695,696],{"class":259,"line":404},[256,697,698],{},"docker compose pull runner\n",[256,700,701],{"class":259,"line":412},[256,702,703],{},"docker compose up -d runner\n",[256,705,706],{"class":259,"line":421},[256,707,708],{},"docker compose logs --tail 100 runner\n",[200,710,711,712,715],{},"For publicly trusted server certificates, omit ",[217,713,714],{},"--ca-file"," and its certificate mount. Keep the Docker socket local to this runner host. No runner ports need publishing.",[200,717,718,719,721],{},"The server hostname must resolve inside the runner container, even when both services run on one host. When they run on separate hosts, use DNS\u002Fprivate routing that reaches the server host's published ports; ",[217,720,206],{}," from another Compose project is not a cross-host DNS name.",[229,723,725],{"id":724},"option-b-linux-service","Option B: Linux service",[200,727,728,729,732],{},"Use a Linux systemd host with a rootful Docker Engine. This example runs the runner as root so it can manage Docker and assign staged output directories to Action UID ",[217,730,731],{},"65532",". Do not give ordinary platform users access to the host Docker socket.",[200,734,735,736,739],{},"Download the matching binary using ",[217,737,738],{},"PLATFORM_TAG"," and the host architecture:",[242,741,743],{"className":244,"code":742,"filename":246,"language":251,"meta":252,"style":252},"export ARCH=amd64\nmkdir -p download\u002Fbinaries\ngh release download \"$PLATFORM_TAG\" --repo pyplines\u002Fpyplines \\\n  --pattern \"pyplines-runner-linux-$ARCH\" --dir download\u002Fbinaries\ngh release download \"$PLATFORM_TAG\" --repo pyplines\u002Fpyplines \\\n  --pattern SHA256SUMS --dir download\n(cd download && sha256sum --check --ignore-missing SHA256SUMS)\nsudo install -m 0755 \"download\u002Fbinaries\u002Fpyplines-runner-linux-$ARCH\" \u002Fusr\u002Flocal\u002Fbin\u002Fpyplines-runner\npyplines-runner version\n",[217,744,745,750,755,760,765,769,774,779,784],{"__ignoreMap":252},[256,746,747],{"class":259,"line":248},[256,748,749],{},"export ARCH=amd64\n",[256,751,752],{"class":259,"line":249},[256,753,754],{},"mkdir -p download\u002Fbinaries\n",[256,756,757],{"class":259,"line":250},[256,758,759],{},"gh release download \"$PLATFORM_TAG\" --repo pyplines\u002Fpyplines \\\n",[256,761,762],{"class":259,"line":341},[256,763,764],{},"  --pattern \"pyplines-runner-linux-$ARCH\" --dir download\u002Fbinaries\n",[256,766,767],{"class":259,"line":404},[256,768,759],{},[256,770,771],{"class":259,"line":412},[256,772,773],{},"  --pattern SHA256SUMS --dir download\n",[256,775,776],{"class":259,"line":421},[256,777,778],{},"(cd download && sha256sum --check --ignore-missing SHA256SUMS)\n",[256,780,781],{"class":259,"line":429},[256,782,783],{},"sudo install -m 0755 \"download\u002Fbinaries\u002Fpyplines-runner-linux-$ARCH\" \u002Fusr\u002Flocal\u002Fbin\u002Fpyplines-runner\n",[256,785,786],{"class":259,"line":437},[256,787,788],{},"pyplines-runner version\n",[200,790,791,792,795],{},"Use ",[217,793,794],{},"arm64"," on AArch64. If the checksum file was already downloaded for the server from the same release, reuse it rather than downloading over it. Confirm both binaries report the same platform version.",[200,797,798],{},"Provision runner state, certificates, and enrollment material:",[242,800,802],{"className":244,"code":801,"filename":246,"language":251,"meta":252,"style":252},"sudo install -d -m 0700 \u002Fvar\u002Flib\u002Fpyplines-runner \u002Fetc\u002Fpyplines-runner\nsudo install -m 0600 enrollment-token \u002Fetc\u002Fpyplines-runner\u002Fenrollment-token\nsudo install -m 0644 ca.crt \u002Fetc\u002Fpyplines-runner\u002Fca.crt\nsudo docker volume create --driver local \\\n  --opt type=none --opt o=bind \\\n  --opt device=\u002Fvar\u002Flib\u002Fpyplines-runner pyplines-runner-01-work\nsudo docker volume inspect pyplines-runner-01-work\nsudo docker network inspect pyplines-actions >\u002Fdev\u002Fnull 2>&1 || sudo docker network create pyplines-actions\n",[217,803,804,809,814,819,824,829,834,839],{"__ignoreMap":252},[256,805,806],{"class":259,"line":248},[256,807,808],{},"sudo install -d -m 0700 \u002Fvar\u002Flib\u002Fpyplines-runner \u002Fetc\u002Fpyplines-runner\n",[256,810,811],{"class":259,"line":249},[256,812,813],{},"sudo install -m 0600 enrollment-token \u002Fetc\u002Fpyplines-runner\u002Fenrollment-token\n",[256,815,816],{"class":259,"line":250},[256,817,818],{},"sudo install -m 0644 ca.crt \u002Fetc\u002Fpyplines-runner\u002Fca.crt\n",[256,820,821],{"class":259,"line":341},[256,822,823],{},"sudo docker volume create --driver local \\\n",[256,825,826],{"class":259,"line":404},[256,827,828],{},"  --opt type=none --opt o=bind \\\n",[256,830,831],{"class":259,"line":412},[256,832,833],{},"  --opt device=\u002Fvar\u002Flib\u002Fpyplines-runner pyplines-runner-01-work\n",[256,835,836],{"class":259,"line":421},[256,837,838],{},"sudo docker volume inspect pyplines-runner-01-work\n",[256,840,841],{"class":259,"line":429},[256,842,843],{},"sudo docker network inspect pyplines-actions >\u002Fdev\u002Fnull 2>&1 || sudo docker network create pyplines-actions\n",[200,845,846,847,850,851,854],{},"The Docker named volume binds to the same host directory the runner uses. Verify its ",[217,848,849],{},"Options.device"," is ",[217,852,853],{},"\u002Fvar\u002Flib\u002Fpyplines-runner","; creating a volume with an existing name does not update its options. Choose a fresh name if that name already belongs to another runner. Never replace a volume containing unfinished execution state.",[200,856,857,858,318],{},"Save this unit as ",[217,859,860],{},"\u002Fetc\u002Fsystemd\u002Fsystem\u002Fpyplines-runner.service",[242,862,867],{"className":863,"code":864,"filename":865,"language":866,"meta":252,"style":252},"language-ini shiki shiki-themes pyplines-light pyplines-light pyplines-dark","[Unit]\nDescription=Pyplines runner\nWants=network-online.target\nRequires=docker.service\nAfter=network-online.target docker.service\n\n[Service]\nType=simple\nUser=root\nStateDirectory=pyplines-runner\nStateDirectoryMode=0700\nUMask=0022\nEnvironment=PYPLINES_RUNNER_STATE=\u002Fvar\u002Flib\u002Fpyplines-runner\nEnvironment=PYPLINES_WORK_VOLUME=pyplines-runner-01-work\nEnvironment=PYPLINES_ACTION_NETWORK=pyplines-actions\nEnvironment=DOCKER_HOST=unix:\u002F\u002F\u002Fvar\u002Frun\u002Fdocker.sock\nExecStart=\u002Fusr\u002Flocal\u002Fbin\u002Fpyplines-runner start \\\n  --server https:\u002F\u002Fpyplines.example.com:8080 \\\n  --ca-file \u002Fetc\u002Fpyplines-runner\u002Fca.crt \\\n  --enrollment-token-file \u002Fetc\u002Fpyplines-runner\u002Fenrollment-token \\\n  --name runner-01 \\\n  --max-concurrent 1\nRestart=on-failure\nRestartSec=5s\nTimeoutStopSec=60s\n\n[Install]\nWantedBy=multi-user.target\n","pyplines-runner.service","ini",[217,868,869,880,892,902,912,922,928,937,947,957,967,977,987,1002,1015,1029,1043,1053,1058,1063,1068,1073,1078,1088,1098,1108,1112,1121],{"__ignoreMap":252},[256,870,871,874,877],{"class":259,"line":248},[256,872,873],{"class":372},"[",[256,875,876],{"class":368},"Unit",[256,878,879],{"class":372},"]\n",[256,881,882,886,889],{"class":259,"line":249},[256,883,885],{"class":884},"sYpJS","Description",[256,887,888],{"class":372},"=",[256,890,891],{"class":368},"Pyplines runner\n",[256,893,894,897,899],{"class":259,"line":250},[256,895,896],{"class":884},"Wants",[256,898,888],{"class":372},[256,900,901],{"class":368},"network-online.target\n",[256,903,904,907,909],{"class":259,"line":341},[256,905,906],{"class":884},"Requires",[256,908,888],{"class":372},[256,910,911],{"class":368},"docker.service\n",[256,913,914,917,919],{"class":259,"line":404},[256,915,916],{"class":884},"After",[256,918,888],{"class":372},[256,920,921],{"class":368},"network-online.target docker.service\n",[256,923,924],{"class":259,"line":412},[256,925,927],{"emptyLinePlaceholder":926},true,"\n",[256,929,930,932,935],{"class":259,"line":421},[256,931,873],{"class":372},[256,933,934],{"class":368},"Service",[256,936,879],{"class":372},[256,938,939,942,944],{"class":259,"line":429},[256,940,941],{"class":884},"Type",[256,943,888],{"class":372},[256,945,946],{"class":368},"simple\n",[256,948,949,952,954],{"class":259,"line":437},[256,950,951],{"class":884},"User",[256,953,888],{"class":372},[256,955,956],{"class":368},"root\n",[256,958,959,962,964],{"class":259,"line":445},[256,960,961],{"class":884},"StateDirectory",[256,963,888],{"class":372},[256,965,966],{"class":368},"pyplines-runner\n",[256,968,969,972,974],{"class":259,"line":453},[256,970,971],{"class":884},"StateDirectoryMode",[256,973,888],{"class":372},[256,975,976],{"class":368},"0700\n",[256,978,979,982,984],{"class":259,"line":461},[256,980,981],{"class":884},"UMask",[256,983,888],{"class":372},[256,985,986],{"class":368},"0022\n",[256,988,989,992,994,997,999],{"class":259,"line":469},[256,990,991],{"class":884},"Environment",[256,993,888],{"class":372},[256,995,996],{"class":884},"PYPLINES_RUNNER_STATE",[256,998,888],{"class":372},[256,1000,1001],{"class":368},"\u002Fvar\u002Flib\u002Fpyplines-runner\n",[256,1003,1004,1006,1008,1010,1012],{"class":259,"line":477},[256,1005,991],{"class":884},[256,1007,888],{"class":372},[256,1009,658],{"class":884},[256,1011,888],{"class":372},[256,1013,1014],{"class":368},"pyplines-runner-01-work\n",[256,1016,1017,1019,1021,1024,1026],{"class":259,"line":485},[256,1018,991],{"class":884},[256,1020,888],{"class":372},[256,1022,1023],{"class":884},"PYPLINES_ACTION_NETWORK",[256,1025,888],{"class":372},[256,1027,1028],{"class":368},"pyplines-actions\n",[256,1030,1031,1033,1035,1038,1040],{"class":259,"line":493},[256,1032,991],{"class":884},[256,1034,888],{"class":372},[256,1036,1037],{"class":884},"DOCKER_HOST",[256,1039,888],{"class":372},[256,1041,1042],{"class":368},"unix:\u002F\u002F\u002Fvar\u002Frun\u002Fdocker.sock\n",[256,1044,1045,1048,1050],{"class":259,"line":507},[256,1046,1047],{"class":884},"ExecStart",[256,1049,888],{"class":372},[256,1051,1052],{"class":368},"\u002Fusr\u002Flocal\u002Fbin\u002Fpyplines-runner start \\\n",[256,1054,1055],{"class":259,"line":515},[256,1056,1057],{"class":368},"  --server https:\u002F\u002Fpyplines.example.com:8080 \\\n",[256,1059,1060],{"class":259,"line":357},[256,1061,1062],{"class":368},"  --ca-file \u002Fetc\u002Fpyplines-runner\u002Fca.crt \\\n",[256,1064,1065],{"class":259,"line":358},[256,1066,1067],{"class":368},"  --enrollment-token-file \u002Fetc\u002Fpyplines-runner\u002Fenrollment-token \\\n",[256,1069,1070],{"class":259,"line":548},[256,1071,1072],{"class":368},"  --name runner-01 \\\n",[256,1074,1075],{"class":259,"line":559},[256,1076,1077],{"class":368},"  --max-concurrent 1\n",[256,1079,1080,1083,1085],{"class":259,"line":567},[256,1081,1082],{"class":884},"Restart",[256,1084,888],{"class":372},[256,1086,1087],{"class":368},"on-failure\n",[256,1089,1090,1093,1095],{"class":259,"line":359},[256,1091,1092],{"class":884},"RestartSec",[256,1094,888],{"class":372},[256,1096,1097],{"class":368},"5s\n",[256,1099,1100,1103,1105],{"class":259,"line":583},[256,1101,1102],{"class":884},"TimeoutStopSec",[256,1104,888],{"class":372},[256,1106,1107],{"class":368},"60s\n",[256,1109,1110],{"class":259,"line":591},[256,1111,927],{"emptyLinePlaceholder":926},[256,1113,1114,1116,1119],{"class":259,"line":599},[256,1115,873],{"class":372},[256,1117,1118],{"class":368},"Install",[256,1120,879],{"class":372},[256,1122,1123,1126,1128],{"class":259,"line":610},[256,1124,1125],{"class":884},"WantedBy",[256,1127,888],{"class":372},[256,1129,1130],{"class":368},"multi-user.target\n",[200,1132,1133,1136,1137,1140],{},[217,1134,1135],{},"UMask=0022"," allows ordinary staged input directories to be read by the Action user. The runner explicitly creates its identity and private state with restricted permissions. Do not apply the server unit's ",[217,1138,1139],{},"0077"," umask to this runner example.",[242,1142,1144],{"className":244,"code":1143,"filename":246,"language":251,"meta":252,"style":252},"sudo systemctl daemon-reload\nsudo systemctl enable --now pyplines-runner\nsudo systemctl status pyplines-runner\nsudo journalctl -u pyplines-runner -n 100 --no-pager\n",[217,1145,1146,1151,1156,1161],{"__ignoreMap":252},[256,1147,1148],{"class":259,"line":248},[256,1149,1150],{},"sudo systemctl daemon-reload\n",[256,1152,1153],{"class":259,"line":249},[256,1154,1155],{},"sudo systemctl enable --now pyplines-runner\n",[256,1157,1158],{"class":259,"line":250},[256,1159,1160],{},"sudo systemctl status pyplines-runner\n",[256,1162,1163],{"class":259,"line":341},[256,1164,1165],{},"sudo journalctl -u pyplines-runner -n 100 --no-pager\n",[229,1167,1169],{"id":1168},"approve-and-verify","Approve and verify",[200,1171,1172],{},"Back on the administrator workstation:",[242,1174,1176],{"className":244,"code":1175,"filename":246,"language":251,"meta":252,"style":252},"pyplines --json runner list\npyplines runner approve RUNNER_ID\npyplines --json runner list\n",[217,1177,1178,1183,1188],{"__ignoreMap":252},[256,1179,1180],{"class":259,"line":248},[256,1181,1182],{},"pyplines --json runner list\n",[256,1184,1185],{"class":259,"line":249},[256,1186,1187],{},"pyplines runner approve RUNNER_ID\n",[256,1189,1190],{"class":259,"line":250},[256,1191,1182],{},[200,1193,1194,1195,1198,1199,1202,1203,1206,1207,1210],{},"Replace ",[217,1196,1197],{},"RUNNER_ID"," with the ID for this newly registered node. Admission should become ",[217,1200,1201],{},"approved","; allow time for the runner to connect and send heartbeats. Confirm ",[217,1204,1205],{},"last_seen"," advances, ",[217,1208,1209],{},"draining"," is false, and it belongs to the intended pool before running work. Approval alone does not establish Docker readiness or successful Action execution.",[200,1212,1213,1214,1217],{},"After registration is saved in ",[217,1215,1216],{},"identity.json",", the runner reuses that identity on restart and no longer reads the enrollment token. Remove the token files and the administrator's enrollment JSON after registration completes. Keep the state volume and server URL unchanged. The Compose example mounts the enrollment directory so removing its token does not invalidate a required file mount.",[200,1219,1220],{},"Changing the server URL requires deliberate identity migration or new enrollment; an existing identity is tied to its original server origin. Do not solve a connection error by deleting the identity of a runner with unfinished work.",[229,1222,1224],{"id":1223},"lifecycle-and-networks","Lifecycle and networks",[200,1226,1227,1228,1231,1232,1235],{},"Both examples use ",[217,1229,1230],{},"on-failure"," restarts so a clean exit after an administrative drain is not immediately restarted. For the Compose method, arrange host startup through your deployment manager or run ",[217,1233,1234],{},"docker compose up -d runner"," after host reboot. For planned shutdown, settle work first; the runner has a bounded shutdown reconciliation window, not an unlimited wait for Actions.",[200,1237,1238,1239,1242,1243,227],{},"An Environment requiring Action network access must specify ",[217,1240,1241],{},"network: pyplines-actions"," to match these examples. Provision that network on every runner host assigned to that Environment. Actions that do not require networking use ",[217,1244,1245],{},"none",[200,1247,1248,1249,1251,1252,1254,1255,227],{},"Continue with ",[204,1250,53],{"href":54},". For all settings and common startup errors, see ",[204,1253,191],{"href":192},". For draining, revoking, and replacing nodes, see ",[204,1256,121],{"href":122},[1258,1259,1260],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .smnRP, html code.shiki .smnRP{--shiki-light:#243247;--shiki-default:#243247;--shiki-dark:#DCE6F0}html pre.shiki code .sgMYo, html code.shiki .sgMYo{--shiki-light:#54657A;--shiki-default:#54657A;--shiki-dark:#A9B8CA}html pre.shiki code .sfDcV, html code.shiki .sfDcV{--shiki-light:#34694B;--shiki-default:#34694B;--shiki-dark:#9BC9AD}html pre.shiki code .sYpJS, html code.shiki .sYpJS{--shiki-light:#806000;--shiki-default:#806000;--shiki-dark:#F2CD66}",{"title":252,"searchDepth":249,"depth":249,"links":1262},[1263,1264,1265,1266,1267],{"id":231,"depth":249,"text":232},{"id":289,"depth":249,"text":290},{"id":724,"depth":249,"text":725},{"id":1168,"depth":249,"text":1169},{"id":1223,"depth":249,"text":1224},"Prepare Docker and persistent work storage, enroll a runner, and verify admission.","md",{},{"title":49,"description":1268},"DO5gV9j3cGZ47-xSMOS6nVfw8CpotamPTdL_5J588w0",[1274,1276],{"title":45,"path":46,"stem":47,"description":1275,"children":-1},"Deploy a persistent Pyplines server with TLS and its first administrator account.",{"title":53,"path":54,"stem":55,"description":1277,"children":-1},"Connect a registry and Environment, execute a known Action, and check retained evidence.",1792213696932]