Azure Pipelinesは、Azure DevOpsに含まれるビルド・テスト・デプロイの自動化サービスです。リポジトリにazure-pipelines.ymlを1本置くと、プッシュのたびにビルドとテストが走り、承認を挟んで本番へ配布するところまで動きます。動かないときは、YAMLの構文より先にMicrosoftホステッドの無料枠が有効になっているかと、トリガーの設定を確認します。Microsoft Learnの現行仕様(2026年10月時点)を基準に、作成からテスト・キャッシュ・トリガー・承認付きデプロイ・テンプレートまで、手を動かす順に整理します。
まとめ
- Microsoftホステッドの無料枠(並列1・1回60分・月1,800分)は自動では付かない。組織をAzureサブスクリプションにリンクして課金を設定すると、プライベートプロジェクトに適用される。
- 並列ジョブは1本あたり月$40(Microsoftホステッド)、$15(セルフホステッド)。Microsoftホステッドは無料枠から1本目を買っても同時実行数は1のままで、月1,800分の制限が外れ、1ジョブの上限が60分から360分に延びるだけ。
- 作成は「Pipelines → New pipeline」でリポジトリを選び、提案されたテンプレートからazure-pipelines.ymlを生成する。骨格はtrigger・pool・stepsの3つ。
- Azure ReposのリポジトリではYAMLの
prは効かない。プルリクエストでの検証はブランチポリシーのビルド検証で設定する。 ubuntu-latestは2026年10月19日から11月19日にかけてUbuntu 24.04から26.04へ順次切り替わっている最中で、windows-latestもVisual Studio 2026版へ切り替わる予定。依存の強いパイプラインはイメージを版で固定する。- 承認待ちのステージは並列ジョブを消費しない。無料枠のままでも承認付きの段階デプロイは組める。
DevOpsパイプラインとAzure Pipelinesの位置づけ
DevOpsパイプラインは、コードの変更を本番に届けるまでの工程を自動化した流れです。ソース取得、ビルド、テスト、成果物(アーティファクト)の保存、各環境へのデプロイという段階に分かれ、前半がCI(継続的インテグレーション)、後半がCD(継続的デリバリー)にあたります。概念と導入判断はCI/CDとは:仕組み・パイプライン構成・GitHub Actionsの設定例と導入判断の基準を解説で扱っており、この記事はAzure Pipelinesで実際に組む手順に絞ります。
Azure Pipelinesは、リポジトリのAzure Repos、作業項目管理のAzure Boards、パッケージ管理のAzure Artifactsと同じ組織・プロジェクトの権限で動きます。リポジトリはGitHubやBitbucket Cloudも選べます。UIの日本語化の可否とクラウド版・オンプレミス版の違いはAzure DevOpsは日本語化できる?UIを日本語にする方法とServices/Serverの違いにまとめています。
YAMLとクラシックエディターの選択
パイプラインの定義方法には、YAMLファイルで書く方式と、画面でタスクを積み上げるクラシックエディターがあります。今から覚えるならYAMLです。パイプラインのセキュリティ方針(Microsoft Learn)のとおり、Sprint 226以降に作られた組織ではクラシックのビルド・リリースパイプラインの新規作成が既定で無効です。YAMLなら定義がリポジトリに入るので、プルリクエストでレビューでき、ブランチごとに定義を変えられます。デプロイ履歴を残すdeploymentジョブはYAML専用で、環境(Environments)と紐づけて使います。
使い始める前に確認する無料枠と料金
YAMLを書き終えて実行しても「キューに登録済み」のまま進まないなら、構文より先に並列ジョブの割り当てと組織の課金設定を確認してください。
無料枠を有効にする条件
並列ジョブには、Microsoftが用意する実行環境(Microsoftホステッド)と、自前のマシンにエージェントを入れるセルフホステッドの2種類があります。Azure Pipelinesとは(Microsoft Learn)の価格の節にあるとおり、無料レベルの並列ジョブは「有効なAzureサブスクリプションで課金が設定されている組織」に付与されます。組織設定でサブスクリプションをリンクして課金を構成すると、無料付与がプライベートプロジェクトへ自動で適用されます。セルフホステッドの無料枠は課金設定なしで1本付き、組織に有効なVisual Studio Enterpriseサブスクライバーがいれば1人につき1本ずつ増えます。
| 区分 | 同時実行 | 1ジョブの上限 | 月間の上限 | 追加1本の価格 |
|---|---|---|---|---|
| Microsoftホステッド 無料 | 1 | 60分 | 1,800分 | – |
| Microsoftホステッド 有料 | 購入本数 | 360分 | なし | 月$40 |
| セルフホステッド 無料 | 1 | なし | なし | – |
| セルフホステッド 有料 | 無料分+購入本数 | なし | なし | 月$15 |
価格はAzure DevOpsの価格ページの米ドル表示です(2026年10月27日確認)。WindowsとLinuxのMicrosoftホステッドエージェントは2コア・メモリ7GB・SSD 14GB(macOSは別構成)で、ジョブが使える空き容量は10GB以上です。大きなDockerイメージを複数pullするビルドは「no space left on device」で落ちるため、その場合はセルフホステッドかManaged DevOps Poolsを検討します。
並列ジョブ1本目の購入で変わること
Microsoftホステッドの並列ジョブを1本買っても、組織の同時実行数は1のまま増えません。1本目で外れるのは「月1,800分」と「1ジョブ60分」の制限です。2本のパイプラインを同時に流したい組織は2本の購入が必要になります。キューが詰まっているのか、長時間ジョブが60分で打ち切られているのかを見分けずに買うと、待ち行列は解消しません。判断にはエージェントプールの「Analytics」タブにあるプール消費レポートを使い、実行中ジョブが上限に張り付いたままキューが積み上がっているかを確認します。
パブリックプロジェクトの廃止
OSS向けに10本の無料並列ジョブが付くパブリックプロジェクトを前提にした解説は古くなっています。パブリックプロジェクトは廃止が決まって新規作成できず、既存分も2027年にプライベートへ変換されます。公開リポジトリのCIを今からこの無料枠に載せる設計は避けてください。
最初のパイプラインの作成手順
Azure DevOpsの組織とプロジェクトがあり、課金設定が済んでいる前提で進めます。手順は最初のパイプラインの作成(Microsoft Learn)と同じ流れです。
New pipelineからazure-pipelines.ymlを生成する操作
- プロジェクトの左メニューで「Pipelines」を開き、「New pipeline」(初回は「Create Pipeline」)を選ぶ。
- コードの場所としてGitHub・Azure Repos Git・Bitbucket Cloudなどを選ぶ。GitHubの場合はAzure Pipelinesアプリのインストールを承認する。
- リポジトリを選ぶと内容が解析され、Maven・ASP.NET Core・Node.jsなどのテンプレートが提案される。既存のYAMLを使う場合は「Existing Azure Pipelines YAML file」でファイルを指定する。
- 表示されたYAMLを確認して「Save and run」を押すと、azure-pipelines.ymlがリポジトリにコミットされ、初回の実行が始まる。
ファイル名はazure-pipelines.ymlが既定ですが、手順3で別のパスのYAMLを選べるので、モノレポでアプリごとにpipeline.ymlを分ける構成も組めます。
YAMLの骨格(trigger・pool・steps)
YAMLは「いつ動かすか(trigger)」「どこで動かすか(pool)」「何をするか(steps)」の3つが骨格です。リポジトリに.nvmrcとpackage-lock.jsonがあり、npm run buildがdistへ出力するNode.jsアプリを前提に、最小構成を示します。
trigger:
- main
pool:
vmImage: 'ubuntu-24.04'
steps:
- task: UseNode@1
inputs:
versionSource: 'fromFile'
versionFilePath: '.nvmrc'
displayName: 'Node.js のセットアップ'
- script: npm ci
displayName: '依存関係のインストール'
- script: npm run build
displayName: 'ビルド'
- publish: '$(System.DefaultWorkingDirectory)/dist'
artifact: 'drop'
displayName: '成果物の発行'
scriptはシェルコマンドをそのまま実行します。Linuxでは複数行を1つのscriptに書くと途中のコマンドが失敗しても後続が走り、最後のコマンドの終了コードだけで成否が決まるため、上の例ではステップを分けています。taskはAzure Pipelinesが提供する定型処理を呼び出します。UseNode@1のversionSource: 'fromFile'は.nvmrcに書いたバージョンを読むので、手元とCIでNode.jsの版がずれません。タスク名末尾の@1はメジャーバージョンです。メジャーは固定され、マイナーバージョンの更新は自動で適用されます。成果物の発行は古い記事に多いPublishBuildArtifacts@1ではなく、publish(Publish Pipeline Artifactの短縮構文)を使います。ただしPublish Pipeline ArtifactはAzure DevOps Services専用です。
エージェントイメージの選び方(2026年10月の移行状況)
vmImageのエイリアスは定期的に指す先が変わります。Microsoftホステッドエージェントの一覧(2026年10月6日更新)では、次の状態です。
| エイリアス | 現在の実体 | 動き |
|---|---|---|
| ubuntu-latest | Ubuntu 24.04(移行中) | 2026-10-19〜11-19に26.04へ移行 |
| windows-latest | Windows Server 2025+VS 2022 | 近くwindows-2025-vs2026へ切替予定 |
| macOS-latest | macOS 15 | macOS-14は2026-11-02に削除予定 |
ubuntu-latestの切り替えはrunner-imagesの告知でAzure DevOpsも対象と明記され、数週間かけて段階的に展開されます。移行期間中にビルドが急に落ちたら、lsb_release -dを出力するステップを足してOSの版を確かめてから原因を切り分けてください。Ubuntu 22.04は非推奨で、2027年4月17日に削除される予定です。Windows Server 2019は2025年12月31日に提供を終えています。エイリアスのままだと、ある日のビルドから環境が入れ替わります。依存が強いプロジェクトは上の例のようにubuntu-24.04と版で固定し、新しいイメージを別ブランチで確かめてから上げてください。poolを書かないYAMLの既定はubuntu-latestです。
実行結果の確認・キャンセル・再実行
パイプラインを選ぶと実行履歴が並び、実行を開くとステージとジョブの状態、各ステップのログ、成果物、テスト結果を確認できます。実行中は「Cancel」で止め、完了後は「Run new」で同じ定義を再実行します。「…」メニューからはログの一括ダウンロードと、実行の保持(retain)も選べます。
README用の状態バッジは「…」→「Status badge」からMarkdownをコピーして貼ります。注意点が1つあり、プライベートプロジェクトでもバッジへの匿名アクセスは既定で有効です。匿名のバッジAPIからプロジェクト名・ブランチ名・ビルドの状態を読まれたくない場合は、プロジェクト設定の「Pipelines → Settings」で「Disable anonymous access to badges」をオンにします。
テスト結果の公開と依存関係キャッシュの設定
ビルドが通ったら、次はテストの結果を画面で追えるようにし、依存関係のダウンロード時間を削ります。triggerとpoolは最小構成のまま、variablesを追加してstepsを次の内容に差し替えます(ビルドと成果物の発行は残します)。
variables:
npm_config_cache: $(Pipeline.Workspace)/.npm
steps:
- task: UseNode@1
inputs:
versionSource: 'fromFile'
versionFilePath: '.nvmrc'
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | package-lock.json'
restoreKeys: |
npm | "$(Agent.OS)"
path: $(npm_config_cache)
displayName: 'npm キャッシュの復元'
- script: npm ci
displayName: '依存関係のインストール'
- script: npm run build
displayName: 'ビルド'
- script: npx jest --ci --reporters=default --reporters=jest-junit
displayName: 'テスト'
- task: PublishTestResults@2
condition: succeededOrFailed()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/junit.xml'
failTaskOnFailedTests: true
displayName: 'テスト結果の公開'
- publish: '$(System.DefaultWorkingDirectory)/dist'
artifact: 'drop'
PublishTestResults@2によるテスト結果の公開
PublishTestResults@2は、JUnit・NUnit・VSTest(TRX)・xUnit・CTest形式の結果ファイルを読み、実行画面の「Tests」タブに一覧と失敗のスタックトレースを出します。既定の検索パターンは**/TEST-*.xmlなので、jest-junit(devDependenciesに追加しておく)が出力するjunit.xmlのようにファイル名が違う場合はtestResultsFilesを書き換えます。condition: succeededOrFailed()を付けるのは、テストが失敗したときこそ結果を公開したいからです。failTaskOnFailedTestsの既定はfalseで、結果を公開するだけでは失敗テストがあってもタスクは成功扱いになります。.NETのDotNetCoreCLI@2でテストを実行する場合や、Mavenタスクで結果の公開オプションを有効にしている場合は、タスク側が結果を公開するのでこのタスクは不要です。
Cache@2による依存関係キャッシュの再利用
パイプラインキャッシュのkeyは、固定文字列・OS・ロックファイルのハッシュを|でつないで作ります。package-lock.jsonが変わればキーが変わり、restoreKeysの前方一致で直近のキャッシュを部分的に使います。npmでキャッシュするのはnode_modulesではなく、npm_config_cacheで指定した共有キャッシュです。npm ciは実行のたびにnode_modulesを削除するため、そちらをキャッシュしても効きません。
キャッシュは作成後に変更できないため、中身を入れ替えたいときはキーにversion2のような文字列を足します。7日間使われないキャッシュは期限切れになり、プルリクエストの実行は対象ブランチのキャッシュを読めても書き込めません。容量の上限はなく、無料レベルでも追加費用はかかりません。
トリガーの設定(CI・PR・パス絞り込み)
「YAMLを直したのに動かない」ときは、まずトリガーの仕様を疑います。CIとPRで書き方が違い、さらにリポジトリの種類で効き方が変わります。
triggerとprの書き方
trigger:
batch: true
branches:
include:
- main
- release/*
paths:
exclude:
- docs
pr:
branches:
include:
- main
triggerを書かないYAMLは全ブランチへのプッシュで動きます(組織かプロジェクトで「Disable implied YAML CI trigger」をオンにした場合を除く)。branchesを書いた時点で、列挙したブランチ以外は自動実行されません。paths.excludeはドキュメントだけの変更でビルドを走らせないための指定で、パスは大文字と小文字を区別します。batch: trueは実行中に来たプッシュをまとめ、完了後に1回だけ実行します。ただし承認を含む複数ステージのパイプラインでは、前の実行が終わるまで次が始まらないため、承認待ちが長い構成では使わないほうが安全です。
prが効くのはGitHubとBitbucket Cloudのリポジトリです。Azure Repos Gitの公式ドキュメントのとおり、Azure ReposではYAMLにPRトリガーを書けず、対象ブランチのブランチポリシーで「Build validation」を追加して設定します。ドラフトのプルリクエストはポリシーを設定しても実行されません。
パイプラインが動かないときの切り分け
公式のトラブルシューティングが挙げる原因を、画面で確認しやすいものから順に並べます。
- パイプラインの編集画面の「…」→「Triggers」で「Override the YAML trigger from here」がオンになり、YAMLの設定がUIで上書きされている。
- プッシュ先ブランチのYAMLが
trigger: noneになっている、またはブランチ・パスがinclude/exclude条件に合っていない。triggerを省略している場合も、「Disable implied YAML CI trigger」がオンなら自動実行されない。CIの挙動はプッシュ先ブランチのYAMLが決める。 - triggerをテンプレートファイルの中に書いている。テンプレート内のtriggerは無効で、メインのYAMLに書く必要がある。
- triggerやpathsに変数を使っている。変数での指定はサポートされない。
- パイプラインが「Paused」または「Disabled」になっている。
- 実行は作られたが「キューに登録済み」で止まる。これはトリガーではなく実行側の問題で、無料枠の未有効化・並列ジョブの枯渇のほか、セルフホステッドならエージェントがオフラインか、ジョブの要求(demands)を満たすエージェントがいない。
ステージ分割と環境ごとの承認
CIが安定したら、デプロイまでの流れをステージに分けます。構造はstages → jobs → stepsの3階層で、ステージ単位で実行順と承認を制御します。
stagesとdeploymentジョブの記述
stages:
- stage: Build
jobs:
- job: BuildApp
pool:
vmImage: 'ubuntu-24.04'
steps:
- task: UseNode@1
inputs:
versionSource: 'fromFile'
versionFilePath: '.nvmrc'
- script: npm ci
- script: npm run build
- publish: '$(System.DefaultWorkingDirectory)/dist'
artifact: 'drop'
- stage: DeployProd
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployWeb
environment: 'production'
pool:
vmImage: 'ubuntu-24.04'
strategy:
runOnce:
deploy:
steps:
- task: AzureWebApp@1
inputs:
azureSubscription: 'prod-workload-identity'
appType: 'webAppLinux'
appName: 'my-web-app'
package: '$(Pipeline.Workspace)/drop'
この例はdistを配布物にする最小形です(prod-workload-identityとmy-web-appは自分のサービス接続名とアプリ名に置き換えます)。App Service側の起動設定は別に必要で、Node.jsのサーバーアプリなら起動コマンドと実行時の依存関係も配布物に含めます。本番ステージのconditionはmainブランチの実行だけを本番へ進め、PRやreleaseブランチの実行が本番の承認待ちに並ばないようにしています。
deploymentジョブは通常のjobと違いenvironmentに紐づき、どの実行がいつその環境へ入ったかの履歴が残ります。deployフックには成果物のダウンロードが自動で差し込まれ、同じ実行で発行したdropは$(Pipeline.Workspace)/dropに置かれます。逆にソースのcheckoutは自動では行われないので、リポジトリのファイルが要るならcheckout: selfを書きます。承認はYAMLに書かず、「Pipelines → Environments」で対象の環境を開き、「Approvals and checks」に承認者を登録します。デプロイ先のApp Serviceのプランと作成手順はAzure App Serviceとは?機能・プラン別の料金と容量、作成手順を解説【2026年10月】を参照してください。
承認待ちと並列ジョブの消費条件
承認待ちや手動介入待ちの実行は並列ジョブを消費しません。並列ジョブが使われるのは、エージェント上でジョブが実際に動いている時間だけです。そのため無料枠(並列1)のままでも、本番ステージに承認を置き、待っている間に別ブランチのCIを流せます。
変数・シークレット・サービス接続の設定
守る対象は、Azureリソースへの認証情報と、アプリ側の機密値の2つです。置き場所が違うので分けて設計します。
シークレット変数と変数グループ
APIキーや接続文字列はYAMLに書かず、パイプライン変数か、ライブラリの変数グループに置きます。変数の編集画面で「Keep this value secret」を指定すると暗号化して保存され、ログ上でもマスクされます。ただし値の一部分(部分文字列)はマスクされないため、加工した値をログへ出さない運用が前提です。変数グループはAzure Key Vaultとリンクでき、値の実体をKey Vaultに置いたまま参照できます。シークレット変数は環境変数へ自動では展開されないため、スクリプトで使うときはenv:で明示的に渡します。
steps:
- script: ./deploy.sh
env:
API_KEY: $(apiKey)
displayName: 'デプロイ(シークレットを明示的に渡す)'
ワークロードIDフェデレーションと2027年のissuer移行
Azureへのデプロイには、Azure Resource Managerのサービス接続を使います。現在の推奨はワークロードIDフェデレーションで、クライアントシークレットを保存せず、ジョブの実行中だけ有効な短命トークンでAzureに認証します。新規作成では「App registration (automatic)」を選び、資格情報に「Workload identity federation」を指定します。
もう1つ期限があります。サービス接続の変換手順(Microsoft Learn)によると、ワークロードIDフェデレーションの接続で使われてきたAzure DevOps issuerは2027年7月1日に廃止されます。対象はAzureパブリッククラウド向けで、シングルテナントのアプリ登録かマネージドIDを使う接続です。Azure Resource Managerに限らず、Dockerや拡張機能が作った接続も含みます(Azure Governmentなど非パブリッククラウドとマルチテナントアプリは対象外)。新しく作る接続は既定でMicrosoft Entra issuerを使い、旧issuerの接続はサービス接続の一覧に警告付きで表示されるので、「Update」で変換します。シークレットレスにした時点で作業が終わるわけではないので、変換を運用計画に入れておいてください。インフラ側をTerraformで管理しているなら、同じフェデレーション認証の設計はTerraformでAzureを構築する手順|azurerm 5系の認証とbackend設計で扱っています。
テンプレートによる複数パイプラインの共通化
パイプラインが増えると、同じビルド手順のコピーが散らばります。YAMLテンプレートには、手順を差し込むincludesテンプレートと、パイプラインが従う骨格を強制するextendsテンプレートの2種類があります。
# templates/node-build.yml
parameters:
- name: runE2E
type: boolean
default: false
steps:
- script: npm ci && npm test
- ${{ if eq(parameters.runE2E, true) }}:
- script: npm run test:e2e
呼び出し側のazure-pipelines.ymlでは、パラメーターを渡してテンプレートを差し込みます。
steps:
- template: templates/node-build.yml
parameters:
runE2E: true
別リポジトリに置いたテンプレートはresources.repositoriesで参照し、refでタグやコミットに固定できます。固定しないと既定のmainブランチを読むため、テンプレート側の変更が全パイプラインに即時に反映されます。extendsテンプレートは、環境のチェックで「Required template」を指定すると、そのテンプレートを経由しない実行を本番環境で止められます。1回の実行で読み込めるのはYAMLファイル100個・入れ子100階層・解析時のメモリ20MBまでです。typeを付けないパラメーターは文字列として扱われ、'false'も空でない文字列として真と評価されるため、真偽値にはtype: booleanを付けます。
Azure Pipelinesを選ばないほうがよい場面
コードがGitHubにあり、デプロイ先もAzure以外(AWS、Google Cloud、VercelのようなPaaS)で完結するなら、リポジトリと権限・課金を1か所にまとめられるGitHub Actionsを選びます。Azure Pipelinesにすると、リポジトリはGitHub、実行基盤はAzure DevOpsという二重管理になり、権限も課金も両方で設計することになります。Actionsの使い方と料金はGitHub Actionsとは?できること・使い方と最初のワークフロー、料金と上限【2026年10月】、主要なCI/CD製品の無料枠と料金の横並びはCI/CDツール比較|主要11製品の無料枠・料金と選び方【2026年10月】で比べています。
Azure Pipelinesが勝つのは、Azure Boardsの作業項目からデプロイまでを1つの組織で追跡したい場合と、Azureリソースへの権限をサービス接続とEnvironmentsの承認で一元管理したい場合です。
Microsoftホステッドの無料枠で長時間のE2Eテストを頻繁に回す設計も成り立ちません。30分かかるブラウザテストは月60回で1,800分を使い切り、1日数回プッシュするチームなら月の後半に止まります。有料の並列ジョブか、実行時間に上限のないセルフホステッドエージェントを導入時点で決めておくべきです。YAMLのジョブ自体にも既定で60分のタイムアウトがあるため、長いジョブにはtimeoutInMinutesも指定します。CI/CDの設計から運用の定着まで外部の手を借りる場合は、DevOps・CI/CD導入支援で相談を受けています。
よくある質問
Azure Pipelinesは無料で使えますか?
プライベートプロジェクトなら、Microsoftホステッドで並列1・1回60分・月1,800分まで無料です。ただし自動では付与されず、組織をAzureサブスクリプションにリンクして課金を設定したときに適用されます。セルフホステッドの無料枠(並列1・時間制限なし)は課金設定なしで使えます。
並列ジョブを1本買えば2本同時に実行できますか?
できません。1本目の購入で外れるのは月1,800分と1ジョブ60分の制限で、同時実行数は1のままです。2本を同時に実行するには、無料レベルから始めた組織は2本を購入します。
azure-pipelines.yml以外のファイル名やパスでも使えますか?
使えます。パイプライン作成時に「Existing Azure Pipelines YAML file」を選ぶと、リポジトリ内の任意のパスのYAMLを指定できます。1つのリポジトリに複数のパイプラインを作り、アプリごとにYAMLを分けることもできます。
Azure PipelinesとGitHub Actionsの違いは何ですか?
YAMLでジョブとステップを書く実行モデルは近く、違いは管理の置き場所です。Azure PipelinesはAzure DevOps組織の権限・課金の下で動き、Azure Boardsの作業項目やEnvironmentsの承認と結びつきます。GitHub Actionsはリポジトリと同じ権限で完結します。リポジトリがGitHubだけで、デプロイ先もAzure以外ならActionsのほうが管理対象は減ります。
Azure DevOps Server(オンプレミス)でも同じ使い方ですか?
YAMLの基本構文は同じですが、Microsoftホステッドエージェントは使えず、セルフホステッドエージェントを自分で用意します。タスクにも差があり、この記事のpublish(Publish Pipeline Artifact)はServerでは使えないため、成果物の発行にはPublishBuildArtifacts@1を使います。現行版は年号の付かない「Azure DevOps Server」で、2025年12月9日にリリースされています。