# Como Criar um Self-Hosted Agent do Azure DevOps

## Introdução

Em um cenário de desenvolvimento ágil e integração contínua, a flexibilidade é importante. É nesse contexto que os Self-Hosted Agents do Azure DevOps emergem como uma solução poderosa, oferecendo controle total sobre seu ambiente de build e deploy.

Neste artigo, exploraremos passo a passo como criar seu próprio Agent Auto Hospedado do Azure DevOps. Essa abordagem não apenas amplia a capacidade de personalização do seu pipeline, mas também proporciona maior eficiência ao permitir a execução de builds em um ambiente familiar e sob seu controle direto.

---

## Tipos de Agents do Azure DevOps

O Azure DevOps oferece flexibilidade na execução de pipelines de CI/CD através de três tipos de Agents:

* `Microsoft-Hosted Agents`
    
* `Self-Hosted Agents`
    
* `Azure Virtual Machine Scale Set Agents`
    

### Microsoft-Hosted Agents

* **Servidores Gerenciados pela Microsoft:** Os Microsoft-Hosted Agents são servidores de build e deploy totalmente gerenciados e atualizados pela Microsoft e estão pronto para uso sem qualquer configuração adicional.
    
* **Limite Gratuito de Minutos por Mês:** no momento da criação deste artigo, existe um limite de 1.800 minutos gratuitos por mês para o uso, oferecendo uma opção econômica para projetos menores.
    
* **Solicitação de Liberação de Minutos:** A Microsoft começou a exigir a solicitação através de um formulário para liberar os 1.800 minutos gratuitos para evitar abusos, se você não o fizer receberá um erro parecido com:
    

<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">No hosted parallelism has been purchased or granted</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708268602951/0bb57224-d1e3-4bef-b566-bee9d300f70c.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Essa mensagem de erro acontece porque por padrão as novas contas criadas recebem <code>0Microsoft-Hosted</code> :</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708268810729/77f12388-f2e5-4135-9dc1-9d8f6a2f4821.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E para resolver isso, você pode clicar no link do próprio erro para preencher o formulário de liberação</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708268999597/8445792e-5b48-4069-99bb-35fe69038be3.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708269048611/9ae930f4-c31e-434a-a24e-a3aa83f40f66.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Uma vez preenchido, você receberá por email um retorno dentro de 2 a 3 dias úteis</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708269186272/3af24fa8-b3a9-43bf-bf8e-ac4ca5083339.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E isso será refletido em sua conta</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708270129731/39765e02-faf5-4a3c-beae-0ad1dc93ea1b.png align="center")

---

### Self-Hosted Agents

* **Controle Total:** Você precisa hospedar ou gerenciar o servidor. O self-hosted oferece controle total ao permitir que você configure e mantenha seus próprios agentes de build.
    
* **Adaptabilidade ao Ambiente:** Permite a execução de builds em um ambiente que você controla, com a flexibilidade de adaptar recursos conforme necessário.
    
* **Adequado para Projetos Complexos:** Ideal para projetos maiores, equipes que requerem ambientes específicos ou aqueles que precisam integrar com recursos internos.
    
* **Cobrança:** Você tem a cobrança do seu servidor, mas seus minutos são ilimitados e você não será cobrado pelo limite e preço do Azure DevOps.
    

---

### Azure Virtual Machine Scale Set Agents

* **Elasticidade com Azure Virtual Machine Scale Set:** Os Azure Virtual Machine Scale Set Agents oferecem elasticidade, permitindo a escala automática de máquinas virtuais conforme a demanda de build e deploy.
    
* **Integração com Azure DevOps:** Projetados para integração direta com o Azure DevOps, esses agentes fornecem um ambiente escalável e gerenciável para pipelines de CI/CD.
    
* **Configuração Sob Demanda:** Permitem a configuração sob demanda de máquinas virtuais, garantindo recursos adequados para lidar com a carga de trabalho, com a flexibilidade de ajustar conforme necessário.
    

---

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Neste artigo, nos concentraremos no Self-Hosted Agent, ainda há vários tipos de instalação como por exemplo em Windows, Linux e Docker, nos concentraremos também na instalação em Docker para proporcionar maior isolamento e portabilidade.</div>
</div>

---

## Benefícios do Self-Hosted Agents

* **Configurações de build personalizadas:** os agents permitem que você configure seu hardware para diferentes tipos de build. Por exemplo, se seu build tiver que consumir muitos recursos, usar hardware com mais memória pode melhorar o tempo de execução.
    
* **Acesse aplicativos ou bancos de dados internos:** ao executar pipelines na infraestrutura do Azure DevOps com Microsoft-Hosted Agents, dificulta o trabalho para poder acessar sistemas internos como banco de dados por exemplo. Se você precisa executar testes de integração em seus bancos de dados ou aplicativos internos, você poderá fazer isso com agents auto hospedados na mesma rede fornecendo acesso necessário aos serviços internos.
    
* **Fluxos de trabalho híbridos**: você pode otimizar seus recursos usando agents auto hospedados com configurações personalizadas para builds que exigem isso e usar a infraestrutura do Azure DevOps com Microsoft-Hosted Agents para outros trabalhos.
    

---

## Tutorial Prático - Pré-Requisitos

Para a instalação de um Agent auto hospedado do Azure DevOps, como o nome sugere você precisará de uma máquina, isso pode ser desde seu próprio computador até qualquer VPS de sua preferência.

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Para o tutorial deste artigo, usarei uma instância do AWS Lightsail, pois seu pacote de instância + tráfego de rede é ofertado a um preço mais acessível. Mas novamente você pode escolher de acordo com sua preferência como <code>AWS EC2</code>, <code>Google Compute Engine</code>, <code>Digital Ocean</code>, <code>Vultr</code>, <code>Linode</code>, etc.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Se você já é mais experiente, ou se não deseja usar o Lightsail, você pode ignorar as etapas abaixo de criação de instância, criação de IP fixo e conexão SSH, não importa qual VPS você criou. Você pode pular diretamente para etapa de configuração dentro da instância e encurtar seu caminho deste artigo, mas se você é mais iniciante ou se está um pouco confuso nesse momento, recomendo seguir todos os passos.</div>
</div>

---

### Criando uma instância Lightsail

1- Acesse o console **AWS → Lightsail → Create Instance**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708102124604/20d8b633-3829-489c-bd43-3905a0c2ba6d.png align="center")

2- Escolha a **região**

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Em nosso exemplo, escolhi a região <code>Norte da Virgínia</code> e a <code>Zona de Disponibilidade A</code></div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708102197827/088a9e8f-9e80-4950-9189-e8e39ebf7bb9.png align="center")

3- Escolha o **sistema operacional**

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Nem todos os sistemas operacionais são suportados, então escolhi a opção <code>Ubuntu 22.04</code> que é a mais recente no momento da criação deste artigo</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708102303030/f881fb52-174c-46e5-b6fe-e4614fb8fa4b.png align="center")

4- Não adicione um `script de inicialização`, não altere sua `chave key-pair` e não habilite `snapshots automáticos`, as opções padrões parecem boas aqui.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708102404328/8c155344-8870-4bf8-93cc-7d9a36ade383.png align="center")

5- Escolha `Dual Stack`, pois ainda há limitações para uso do `IPv6` e precisamos que nossa **instância tenha IPv4 também**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708102502783/ceb3b2da-e727-43a4-9fa2-837bf2485ed7.png align="center")

6- Selecione o **tamanho da instância**

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Lembre-se que essa decisão é muito importante, quanto menor a instância você escolher, mais lentidão e problemas seu pipeline pode ter por conta de falta de memória ou cpu. Para nosso artigo escolhi a opção <code>small de USD 10 com 2GB de memória, 2 vCPU e 3TB de transferência de dados</code>.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">Em minhas experiências, as instâncias abaixo de <code>1GB de memória não chegam a funcionar corretamente</code>, e tenho usado <code>instâncias acima de 4GB de memória em ambientes de produção</code> . Mas para efeitos de tutorial essa parece ser uma escolha boa.</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708102665985/a88dfcb6-207d-4edc-a16b-69430e9cf42d.png align="center")

7- Defina um **nome**, opcionalmente você pode inserir **tags** e clique em **criar instância**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271196101/91ec0f1c-444a-439b-8989-ba9f1ee3738b.png align="center")

8- Aguarde até o status `Pending` mudar para `Running`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271234209/02924998-bba9-4629-abf5-628912afd0ed.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271303179/bf5e2bf3-d540-4105-8edf-2632703b6a47.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Pronto! Nesse momento, você já tem uma instância criada com sucesso.</div>
</div>

---

### Criando um IP Fixo

Se olharmos bem, a instância Lightsail recebeu um `IPv4` e `IPv6` público dinâmicos, isso aconteceu porque selecionamos a opção `Dual Stack` quando criamos a instância. O dinâmico significa que quando desligarmos essa instância e ligarmos novamente, esses endereços IPs irão mudar, isso pode ser um pouco chato e frustrante de se lembrar, principalmente quando você está tentando se conectar a ela.

Então vamos resolver isso criando um IP Fixo.

1- No console **AWS Lightsail → Networking**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271357491/c7032e49-2b4f-4768-b70c-ed681bacefba.png align="center")

2- Clique em **Create Static IP**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708103734361/c813a5b9-1aff-4353-b094-3f51890f1b31.png align="center")

3- Mantenha a **mesma região e zona de disponibilidade da sua instância → escolha a instância recém criada**

Em nosso exemplo `Norte da Virgínia` e `Zona de Disponibilidade A`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271389920/817448cc-5af9-4fba-85b2-b53e211cbb99.png align="center")

4- Defina um **nome → Create**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271446677/4e7e427e-c635-4f0e-a8ae-a1ef400a1e0e.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271477358/a2a13b9a-ae87-4542-b106-a89ac34b2dfc.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Pronto! Agora já temos um IP Fixo anexado a nossa instância Lightsail</div>
</div>

---

### Conectando via SSH na instância Lightsail

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Você pode fazer isso de muitas formas, com qualquer cliente SSH de sua preferência.</div>
</div>

Se você não quiser usar um cliente SSH, pode fazer isso diretamente no console da AWS clicando nesse botão:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271540590/4d85d6f0-6d7b-4ed3-8ddc-81f4b3884c8c.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E isso irá abrir uma nova janela com seu acesso com sucesso:</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271594202/7918e6f1-efb3-47ee-a4f2-6eaa75cebeef.png align="center")

Se você estiver usando uma distribuição `linux`, como por exemplo `ubuntu`, poderia acessar também através do seu terminal com o comando:

```bash
ssh -i <LightsailDefaultKey.pem> <user>@<public-ip>
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">Lembre-se que <code>LightsailDefaultKey.pem</code> é sua chave de acesso baixada do console da AWS, então especifique certinho o caminho da pasta para onde você fez download em sua máquina, também lembre-se que usamos o ubuntu, então mude <code>user</code> para <code>ubuntu</code> e <code>&lt;public-ip&gt;</code> para seu ip fixo criado.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Para nosso exemplo isso seria:</div>
</div>

```bash
# Example
ssh -i LightsailDefaultKey.pem ubuntu@54.160.152.44
```

Porém como estou usando em meu computador o sistema operacional Windows, eu poderia fazer isso através do `WSL2` com `Prompt de Comando` , `Powershell` ou `Terminal do Windows`, o `Putty` e muitas outras opções de clientes SSH disponíveis.

Mas prefiro usar o cliente gratuito chamado `Termius`, você pode obtê-lo em sua página de download e talvez seja necessário criar uma conta gratuita, há planos pagos disponíveis, mas o gratuito aqui é suficiente.

<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">Importante ressaltar que a escolha do seu cliente SSH é totalmente opcional.</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708104728047/fd5b37ec-1cf1-46ec-b512-b28332f4efc5.png align="center")

Também há opções disponíveis para Mac e Linux no final da página;

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708104779160/b47df59a-d66d-42cc-b834-6707bf38da4b.png align="center")

Não importa qual ferramenta SSH você usará, vimos agora que há muitas opções, tudo que você precisa ter em mente aqui é que você precisa se conectar na instância de alguma forma para seguirmos o tutorial.

---

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Como acabamos de ver, vou utilizar o cliente SSH Termius, então nossos prints irão se basear nisso, mas uma vez que você estiver conectado, os comandos serão todos iguais independente de qual ferramenta usou.</div>
</div>

1- Acesse o console **AWS Lightsail → Account → SSH Keys**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708105477034/74dae1d0-e863-4dcd-a92f-c3a60f4f800a.png align="center")

2- Procure por **Default Keys → download**

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Lembre-se que estou usando <code>Norte da Virgínia</code>, por isso faço download da chave padrão dessa região.</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708105623262/9eaa27e5-2734-43de-8801-bba602c86908.png align="center")

3- Abra o cliente **SSH (em nosso exemplo Termius) → clique no ícone de engrenagem**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708105306882/64124329-09e1-46dd-8992-08233a36715f.png align="center")

4- Defina um **nome de sua preferência → mova o arquivo da sua chave keypair que baixou do console AWS**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708105876870/8b909b8a-cb48-44a4-b6da-955173932330.png align="center")

5- Clique em **New Host**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708106087597/2c8d677c-e421-48c5-825f-9169b0928a02.png align="center")

6- Preencha os campos com:

* `Address`: o IP da instância
    
* `Label`: nome de sua preferência
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271776575/0834f74b-43ff-4136-b969-587908b27146.png align="center")

* `User`: ubuntu
    
* Selecione `Key`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708108034554/720336fb-5c99-4bba-ae23-98697d426e96.png align="center")

Clique em `Connect`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708108020163/f56241d8-e94a-4fc9-b9a3-aec063003d40.png align="center")

Clique em `Continue`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271863447/5dae1208-2837-4850-be0f-bb3cd0419eb1.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708271888449/faa12c55-6496-4f07-b790-c90f6b5620a0.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Pronto! Você está conectado.</div>
</div>

---

### Instalando o Docker e Docker Compose

Uma vez conectado na instância, você precisará instalar o `Docker` e o `Docker Compose`, por favor copie o código de instalação que criamos em outro artigo disponível aqui:

* 👉 [Como Instalar ou Atualizar o Docker e o Docker Compose](https://simplescloud.io/como-instalar-ou-atualizar-o-docker-e-o-docker-compose#heading-instalacao-ou-atualizacao)
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1720618391252/99f1a177-30ff-47ae-946a-b18d315ce1fc.png align="center")

Cole em seu terminal e pressione `Enter`.

Aguarde a instalação e ao final você deverá ver algo parecido com:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708272075878/f11cba3e-5867-4c2a-a4bb-2691f5e937d6.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">Agora feche e abra seu terminal novamente para que os comandos <code>docker</code> sejam atualizados.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text"><strong>Essa instalação é muito importante</strong>, pois precisaremos dela mais a frente para a instalação do self-hosted, além disso muito provavelmente seu agent precisará do docker para baixar e construir imagens em seu pipeline através do <code>docker in doker (dind)</code>. <strong>Então certifique-se de concluir essa instalação antes de seguir aos próximos passos.</strong></div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Pronto! Você está com todos os pré-requisitos deste tutorial concluídos.</div>
</div>

---

### Criando um Personal Access Token (PAT)

1- Faça login no console **Azure DevOps → ícone de engrenagem → Personal Access Token**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708274548107/a4e5dee9-8563-449f-8b47-efe8b33c5a56.png align="center")

2- Clique em New Token

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708274586899/9b5e0353-40fd-4ea2-bf74-880a4e8a7880.png align="center")

3- Defina:

* Um **nome de sua preferência**, por exemplo `self-hosted-agent-token`
    
* Sua organização
    
* Altere o **tempo de expiração de acordo com sua preferência**, por exemplo gosto de definir o tempo máximo de um ano através da seleção no calendário, mas totalmente opcional
    
* Em `scope`, selecione `Custom definied` → `Show all scopes`
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708274984955/33c97ebe-0af3-45de-89fb-9cb8ba999e41.png align="center")

4- Em `Agent Pools` → selecione `Read & manage` → `Create`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708275101098/39d70c02-8efc-4d78-9dda-4ecc023e1028.png align="center")

5- **Copie e salve** seu token em um local seguro agora

<div data-node-type="callout">
<div data-node-type="callout-emoji">⚠</div>
<div data-node-type="callout-text">Isso não será acessível depois</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708275188409/eec059f3-d087-4f54-87e1-b1c9c27fa173.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708275370109/c218bc59-7157-4c54-b373-64a452380c1f.png align="center")

---

## Instalando o Self-Hosted Agent

1- Abra novamente seu terminal → crie o diretório chamado `azp-agent-in-docker` com o comando:

```bash
mkdir ~/azp-agent-in-docker \
&& cd ~/azp-agent-in-docker
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708275503697/2d06375e-bf03-4284-9611-a2454c0af9fc.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Nós criaremos os arquivos chamados <code>Dockerfile</code> e <code>start.sh</code> nesta pasta nos próximos passos, certifique-se que ambos os arquivos estejam na mesma pasta.</div>
</div>

2- Crie o arquivo **Dockerfile** com o comando:

```bash
touch Dockerfile
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708275662986/dd01dcaf-9ab5-4087-8aa9-3997d76e9041.png align="center")

3- Insira o seguinte conteúdo em seu **Dockerfile**:

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Você pode fazer através de qualquer editor de texto de sua preferência como por exemplo, <code>VSCode</code>, <code>vim</code>, <code>nano</code>, etc. Para nosso exemplo estamos usando o <code>nano</code> para editar o arquivo.</div>
</div>

```bash
sudo nano Dockerfile
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708275801763/c73fb75a-c733-4448-95ca-3f0fff30b155.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708275821914/9b663117-34db-490a-910a-93b9b08441d2.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E então eu copio o código abaixo:</div>
</div>

```dockerfile
# Use Ubuntu 22.04 as the base image
FROM ubuntu:22.04

# Update package information and upgrade installed packages
RUN apt update && \
    apt upgrade -y

# Install dependencies and capabilities
RUN apt install -y \
    curl \
    git \
    jq \
    libicu70

# Set the target architecture (e.g., "linux-x64", "linux-arm", "linux-arm64")
ENV TARGETARCH="linux-x64"

# Set the working directory inside the container
WORKDIR /azp/

# Copy the start script to the working directory
COPY ./start.sh ./

# Make the start script executable
RUN chmod +x ./start.sh

# Create a user 'agent' and set ownership of the working directory
RUN useradd agent && \
    chown agent ./

# Switch to the 'agent' user
USER agent

# Another option is to run the agent as root.
# ENV AGENT_ALLOW_RUNASROOT="true"

# Define the entry point for the container to execute the start script
ENTRYPOINT ./start.sh
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E colo no terminal com o botão direito do mouse</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708276149007/188de3fe-b3d0-48e0-8d61-257a225fa07f.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Depois pressiono <code>Ctrl + X</code> + <code>Y</code> (para sim) + <code>Enter</code> para salvar</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">E podemos ver o arquivo criado com o comando:</div>
</div>

```bash
cat Dockerfile
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708276258126/b14c621e-d657-4b46-a5f4-1cdd6a975415.png align="center")

4- Agora crio o arquivo chamado `start.sh` com o comando:

```bash
touch start.sh
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708276460499/44b39808-5d2d-4707-a133-d751cea432a1.png align="center")

5- Insira o seguinte conteúdo em seu `start.sh`:

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Você pode fazer através de qualquer editor de texto de sua preferência como por exemplo, <code>VSCode</code>, <code>vim</code>, <code>nano</code>, etc. Para nosso exemplo estamos usando o <code>nano</code> para editar o arquivo.</div>
</div>

```bash
sudo nano start.sh
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708276529754/92a25f66-f5ea-48c6-a972-208ff1590cb7.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708276544726/483c802d-798e-47d5-9be6-7ce9a1a61f8c.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E então eu copio o código abaixo:</div>
</div>

```bash
#!/bin/bash
set -e

if [ -z "${AZP_URL}" ]; then
  echo 1>&2 "error: missing AZP_URL environment variable"
  exit 1
fi

if [ -z "${AZP_TOKEN_FILE}" ]; then
  if [ -z "${AZP_TOKEN}" ]; then
    echo 1>&2 "error: missing AZP_TOKEN environment variable"
    exit 1
  fi

  AZP_TOKEN_FILE="/azp/.token"
  echo -n "${AZP_TOKEN}" > "${AZP_TOKEN_FILE}"
fi

unset AZP_TOKEN

if [ -n "${AZP_WORK}" ]; then
  mkdir -p "${AZP_WORK}"
fi

cleanup() {
  trap "" EXIT

  if [ -e ./config.sh ]; then
    print_header "Cleanup. Removing Azure Pipelines agent..."

    # If the agent has some running jobs, the configuration removal process will fail.
    # So, give it some time to finish the job.
    while true; do
      ./config.sh remove --unattended --auth "PAT" --token $(cat "${AZP_TOKEN_FILE}") && break

      echo "Retrying in 30 seconds..."
      sleep 30
    done
  fi
}

print_header() {
  lightcyan="\033[1;36m"
  nocolor="\033[0m"
  echo -e "\n${lightcyan}$1${nocolor}\n"
}

# Let the agent ignore the token env variables
export VSO_AGENT_IGNORE="AZP_TOKEN,AZP_TOKEN_FILE"

print_header "1. Determining matching Azure Pipelines agent..."

AZP_AGENT_PACKAGES=$(curl -LsS \
    -u user:$(cat "${AZP_TOKEN_FILE}") \
    -H "Accept:application/json;" \
    "${AZP_URL}/_apis/distributedtask/packages/agent?platform=${TARGETARCH}&top=1")

AZP_AGENT_PACKAGE_LATEST_URL=$(echo "${AZP_AGENT_PACKAGES}" | jq -r ".value[0].downloadUrl")

if [ -z "${AZP_AGENT_PACKAGE_LATEST_URL}" -o "${AZP_AGENT_PACKAGE_LATEST_URL}" == "null" ]; then
  echo 1>&2 "error: could not determine a matching Azure Pipelines agent"
  echo 1>&2 "check that account "${AZP_URL}" is correct and the token is valid for that account"
  exit 1
fi

print_header "2. Downloading and extracting Azure Pipelines agent..."

curl -LsS "${AZP_AGENT_PACKAGE_LATEST_URL}" | tar -xz & wait $!

source ./env.sh

trap "cleanup; exit 0" EXIT
trap "cleanup; exit 130" INT
trap "cleanup; exit 143" TERM

print_header "3. Configuring Azure Pipelines agent..."

./config.sh --unattended \
  --agent "${AZP_AGENT_NAME:-$(hostname)}" \
  --url "${AZP_URL}" \
  --auth "PAT" \
  --token $(cat "${AZP_TOKEN_FILE}") \
  --pool "${AZP_POOL:-Default}" \
  --work "${AZP_WORK:-_work}" \
  --replace \
  --acceptTeeEula & wait $!

print_header "4. Running Azure Pipelines agent..."

chmod +x ./run.sh

# To be aware of TERM and INT signals call ./run.sh
# Running it with the --once flag at the end will shut down the agent after the build is executed
./run.sh "$@" & wait $!
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E colo no terminal com o botão direito do mouse</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708276687384/f0534fd7-6dc2-4c1b-8219-367590fa411f.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Depois pressiono <code>Ctrl + X</code> + <code>Y</code> (para sim) + <code>Enter</code> para salvar</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">E podemos ver o arquivo criado com o comando:</div>
</div>

```bash
cat start.sh
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708276762481/6562245b-a3ed-42ea-b477-790c7731aeea.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Agora podemos ver a criação dos dois arquivos <code>Dockerfile</code> e <code>start.sh</code> criados na mesma pasta <code>azp-agent-in-docker</code> com o comando:</div>
</div>

```bash
ls -1
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708276839453/1085d818-d27d-4bd4-a782-5086a676633a.png align="center")

6- Agora construo a imagem com o comando:

```bash
docker build -t "azp-agent:linux" -f Dockerfile .
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708278082586/60fcaac7-4194-46c7-90f6-d3fb80fdf26a.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E podemos confirmar a imagem docker criada com sucesso com o comando:</div>
</div>

```bash
docker image ls --format "table {{.ID}}\t{{.Repository}}"
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708278138869/c5098ebd-a3fe-49d7-9b31-51decaafb40a.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Pronto! Já temos nossa imagem pronta para uso.</div>
</div>

---

7- Agora vamos **precisar de três informações** para poder iniciar nosso container:

* `AZP_URL`:
    
    Você pode obtê-la olhando para a barra de endereço do seu navegador:
    
    <div data-node-type="callout">
    <div data-node-type="callout-emoji">🔗</div>
    <div data-node-type="callout-text">https://dev.azure.com/<code>&lt;your-organization&gt;</code></div>
    </div>
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708289504114/f8fe1b62-c1f8-4575-9721-50814d852328.png align="center")
    
* `AZP_POOL`:
    
    <div data-node-type="callout">
    <div data-node-type="callout-emoji">ℹ</div>
    <div data-node-type="callout-text">Você precisa criar um novo em<strong> Organization Settings → Agent Pools → Add Pool</strong></div>
    </div>
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708289553968/e4cd223e-e7eb-4fae-a474-b00527e9924d.png align="center")
    
    <div data-node-type="callout">
    <div data-node-type="callout-emoji">ℹ</div>
    <div data-node-type="callout-text">Selecionar o tipo <code>Self-Hosted</code></div>
    </div>
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708278848571/dfd1211d-6705-4537-ab26-2f8a4dbc532e.png align="center")
    
    <div data-node-type="callout">
    <div data-node-type="callout-emoji">ℹ</div>
    <div data-node-type="callout-text">Definir um nome de sua preferência, por exemplo <code>AWS-Lightsail-Self-Hosted-Linux-Docker</code> → manter a opção <code>Auto-provision this agent pool in all projects</code> marcado → <code>Create</code></div>
    </div>
    
    ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708279002740/cecf79df-2928-4ed4-8385-1ec2f841f6b5.png align="center")
    
* `AZP_TOKEN`:
    
    Este é o `Personal Access Token (PAT)` que criamos e copiamos para um local seguro antes.
    

---

8- Com essas três informações em mãos, vamos iniciar o container com o seguinte comando:

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Lembre-se de substituir os valores para seus dados reais</div>
</div>

```bash
docker run \
  -e AZP_URL="<Your Azure DevOps instance>" \
  -e AZP_TOKEN="<Your Personal Access Token>" \
  -e AZP_POOL="<Your Agent Pool Name>" \
  -e AZP_AGENT_NAME="AWS Lightsail Docker Agent 1" \
  --name "azp-agent-1" \
  azp-agent:linux
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708285553922/4c5a384c-0e66-4f9f-9e20-6b609e7289f1.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708285892104/48ebf28a-2078-4414-a153-3d925b01bdbd.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Pronto! Podemos ver o Agent pronto para uso</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708289600401/9d18ec06-e3f6-4c6a-9876-65aa5b10b16e.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">E podemos verificar os <code>capabilities</code> com as ferramentas úteis que foram listadas no Dockerfile</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708289645083/e4c2caf5-7b00-458d-9f2e-d22cd29a64f6.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Por padrão, o container não irá iniciar automaticamente se sua instância for desligada ou reiniciada. Então vamos garantir que o container sempre se inicie com o <code>restart policy</code> deste container do agent</div>
</div>

9- Digite o comando abaixo para atualizar o `restart policy` deste container:

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Lembre-se de alterar <code>&lt;your-container-id&gt;</code> para o ID de seu container obtido com o comando <code>docker ps -a</code> por exemplo</div>
</div>

```bash
docker update --restart always <your-container-id>
```

10- Verifique a inspeção de container com o novo `restart policy` definido:

```bash
docker inspect -f '{{ json .HostConfig.RestartPolicy }}' <your-container-id>
```

<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Pronto! Seu agent auto hospedado já está online e pronto para uso.</div>
</div>

---

## Usando o Self-Hosted Agent

Se você estiver usando o arquivo `azure-pipelines.yml` você pode definir seu Self-Hosted Agent no trecho `pool`, por exemplo:

```yaml
trigger:
- main

pool:
  # Name of your agent pool
  name: 'AWS-Lightsail-Self-Hosted-Linux-Docker'  

jobs:
- job: RunOnSelfHostedAgent
  displayName: 'Run on AWS Lightsail Docker Agent'
  steps:
  - script: echo "Hi, I'm running on AWS Lightsail Docker Agent 1"
    displayName: 'Print Message'
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708287345193/d52a886d-786d-4577-9cdc-3680c7dd53b8.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">E executando o pipeline, podemos visualizar em <code>Initialize Job</code> o agente self-hosted agent que criamos sendo usado.</div>
</div>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708287525837/800602b0-bce0-4d1b-a32b-d224004f9839.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Se voltarmos para nosso terminal, podemos ver exatamente o nome do container igual ao apresentado acima com o comando:</div>
</div>

```bash
docker ps --format "table {{.ID}}\t{{.Names}}" --all
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708287728349/3a2ac737-a2a3-4f69-ba48-ac33ea5264e8.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708287759469/24388508-9753-4775-b590-16081e32b706.png align="center")

---

Se você estiver usando o editor `classic` , poderá fazer isso selecionando o `pool`

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708288030007/136d3039-7d3e-47c1-83b0-6917fbc69e15.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708288133667/c40fc8f6-7674-47f0-829f-3e67e4975d41.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708288152713/f3413722-c159-4e8f-bdbd-368817094086.png align="center")

---

## Bônus - Atualização dos Agents

A qualquer momento, você pode atualizar seus agents em **Organization Settings → Agent Pools → More Options (três pontinhos) → Update Agent**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708288347371/60e14928-99c6-4173-a3d8-5c8783221b5c.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1708288368147/a45cddc8-e02c-4e22-9f0f-e186dd20305b.png align="center")

---

## Conclusão

Em meio ao dinâmico cenário da automação e integração contínua, o Self-Hosted Agent do Azure DevOps emerge como uma ferramenta poderosa para capacitar equipes de desenvolvimento. Neste artigo, exploramos os diferentes tipos de agents disponíveis, destacando a versatilidade e flexibilidade do Self-Hosted Agent.

Ao conduzir um passo a passo prático, mergulhamos na criação de um Self-Hosted Agent utilizando o Docker. Essa abordagem não apenas oferece controle total sobre o ambiente de execução, mas também demonstra a escalabilidade e eficiência proporcionadas pelo uso de containers.

Vimos que os passos são manuais e você pode repeti-los quantas vezes for necessário. Também pode ainda pensar em uma forma de automatizar e escalar a criação de seus Self-Hosted Agents, mas para a maioria das pessoas isso será o suficiente.

Ao concluir esta jornada, espero que você esteja equipado com os conhecimentos necessários para aproveitar ao máximo o potencial do Self-Hosted Agent no Azure DevOps. A capacidade de personalizar, escalar e integrar-se perfeitamente ao seu ambiente torna esse recurso essencial para otimizar seus fluxos de trabalho de CI/CD.

<div data-node-type="callout">
<div data-node-type="callout-emoji">ℹ</div>
<div data-node-type="callout-text">Lembre-se de excluir todos os recursos ao final para não obter cobranças indesejadas e manter sua conta AWS limpa e organizada.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">✅</div>
<div data-node-type="callout-text">Espero que as informações tenham sido úteis para você!</div>
</div>
