\chapter{Tecnologias} Este capítulo apresenta as principais tecnologias empregadas no desenvolvimento do aplicativo, organizadas de acordo com a camada da arquitetura em que atuam: \textit{front-end}, \textit{back-end} e infraestrutura/persistência de dados. \section{Front-end} O termo \textit{frontend} refere-se à camada de um sistema responsável pela interação direta com o usuário: a interface visual, a lógica de apresentação e o processamento que ocorre no dispositivo do próprio usuário, seja um navegador ou, como no caso do Nexo, um aplicativo móvel, conforme ~\citet{sommerville:2010}. É o frontend que renderiza telas, captura entradas do usuário (como toques e formulários) e comunica-se com o \textit{backend} através de requisições de rede, geralmente utilizando APIs. No caso do Nexo, essa camada é implementada com \textit{React Native} e Expo, permitindo que a mesma base de código seja compilada tanto para Android utilizando bibliotecas em \textit{TypeScript}. \subsection{React Native} React Native é um \textit{framework} de código aberto, mantido pela Meta, que permite o desenvolvimento de aplicativos móveis multiplataforma a partir de uma única base de código escrita em JavaScript/TypeScript, utilizando componentes que são traduzidos para elementos nativos tanto no iOS quanto no Android \citep{reactnative:2026}. A escolha do React Native se justifica pelo escopo reduzido da equipe de desenvolvimento (um único desenvolvedor) e facilidade de conhecimento e manutenção, sem trazer, para este projeto, benefícios que justifiquem esse custo adicional. Além disso, o ecossistema maduro do React Native oferece ampla disponibilidade de bibliotecas para funcionalidades já previstas no aplicativo, como notificações locais e gráficos. \subsection{Zustand} Zustand é uma biblioteca leve de gerenciamento de estado para aplicações React e React Native, que dispensa a estrutura verbosa de \textit{providers} e \textit{reducers} tipicamente associada a bibliotecas como Redux \citep{zustand:2026}. Ela foi escolhida para gerenciar o estado efêmero da aplicação (por exemplo, dados de formulários em preenchimento ou estados de navegação temporários) por sua API minimalista e baixo \textit{overhead} de configuração, adequada a um projeto de escopo individual em que a complexidade adicional de soluções mais robustas de gerenciamento de estado não se justifica. \section{Back-end} Já o termo \textit{backend} refere-se à camada de um sistema que processa a lógica de negócio, gerencia o acesso e a persistência de dados, e expõe essa funcionalidade a clientes (como o frontend) através de uma interface bem definida, tipicamente uma API HTTP \cite{Fowler_2013}. O \textit{backend} é responsável por validar dados recebidos, aplicar regras de negócio, autenticar usuários, e coordenar o acesso ao banco de dados e a outros serviços auxiliares. No caso do Nexo, essa camada é implementada em Node.js com o framework \textit{Express}, seguindo uma arquitetura em camadas (\textit{Route} $\rightarrow$ \textit{Controller} $\rightarrow$ \textit{Service} $\rightarrow$ Prisma) que separa as responsabilidades de validação, lógica de negócio e acesso a dados \cite{Martin_2012}. \subsection{Express.js} Express.js é um \textit{framework} minimalista para aplicações web em Node.js, amplamente utilizado para a construção de APIs \textit{REST}, oferecendo uma camada fina de abstração sobre o roteamento de requisições HTTP e \textit{middlewares} \citep{express:2026}. Sua adoção se deve à simplicidade e à flexibilidade não opinativa do \textit{framework}, que permite estruturar a API de acordo com as necessidades específicas do projeto, sem impor convenções rígidas de arquitetura, além de sua ampla documentação e maturidade no ecossistema Node.js. \subsection{Zod} Zod é uma biblioteca de validação e definição de esquemas de dados para TypeScript, que permite declarar formatos esperados de entrada (como corpos de requisição) e inferir automaticamente os tipos estáticos correspondentes \citep{zod:2026}. Zod foi escolhido para validar os dados recebidos pela API antes de seu processamento, garantindo que registros de humor, sono e demais entradas do usuário estejam em conformidade com o formato esperado. A inferência automática de tipos a partir dos esquemas evita a duplicação de definições de tipo entre a camada de validação e o restante do código TypeScript do back-end. \begin{lstlisting}[language=typescript, caption={Validação e Inferência usando Zod}, label={lst:zod-inference}] import { BaseMoodOption, MoodComponentOption, IntensityLevel } from '@prisma/client'; export const MoodComponentSchema = z.object({ component: z.nativeEnum(MoodComponentOption), intensity: z.nativeEnum(IntensityLevel) }); export const CreateMoodSchema = z.object({ annotation: z.string().optional(), moment: z.coerce.date(), selectedMood: z.nativeEnum(BaseMoodOption), anxietyLevel: z.number().int().min(0).max(10), stressLevel: z.number().int().min(0).max(10), energyLevel: z.number().int().min(0).max(10), moodComponents: z.array(MoodComponentSchema).min(1) }); export type CreateMoodInput = z.infer; \end{lstlisting} \sourcecode{Elaborado pelo autor (2026)} Aqui, é possível observar alguns elementos. O primeiro é o uso de \texttt{z.nativeEnum} para reaproveitar diretamente os \textit{enums} declarados no esquema do Prisma (linhas 1--5), em vez de repetir as listas de valores no código. Como esses enums são a mesma fonte de verdade que o banco impõe, a validação aceita exatamente o conjunto de valores que pode ser persistido, e qualquer valor fora dele é rejeitado na borda da API antes de chegar à camada de persistência. Esse padrão se repete de forma extensiva nos demais esquemas, que reutilizam enumerações de gatilho, tipo de ação de cuidado, periodicidade de medicamento e tipo de indicador. Em seguida, são criados os \textit{schemas} de validação: um valida os componentes de humor de forma aninhada dentro do objeto recebido, e o outro valida os dados do próprio registro --- os níveis aceitos de ansiedade, estresse e energia; a conversão do campo \texttt{moment} para um objeto de data a partir da entrada recebida; a exigência de ao menos um componente de humor selecionado; entre outros. Por fim, esses dados são exportados como um \textit{type} do TypeScript, usado pelo compilador para a verificação estática de tipos no restante do back-end. \subsection{Valkey e BullMQ} Valkey é um banco de dados em memória, do tipo chave-valor, criado como \textit{fork} de código aberto do Redis, mantido sob a governança da Linux Foundation \citep{valkey:2026}. BullMQ é uma biblioteca para gerenciamento de filas de tarefas em Node.js, construída sobre bancos de dados compatíveis com o protocolo Redis \citep{bullmq:2026}. A combinação das duas ferramentas foi adotada para o processamento paralelo e assíncrono de tarefas que não precisam bloquear a resposta imediata da API, como o disparo de notificações programadas e o processamento de indicadores a partir do histórico de registros do usuário. A escolha do Valkey em específico, e não do Redis diretamente, se deve à mudança de licenciamento do Redis em 2024, que passou a restringir certos usos comerciais; o Valkey mantém compatibilidade total de protocolo, permanecendo sob licença permissiva de código aberto. \section{Infraestrutura e Persistência de Dados} A camada de \textit{infraestrutura e persistência} de um sistema abrange os componentes responsáveis por armazenar e gerenciar os dados de forma durável, bem como os serviços auxiliares que sustentam o funcionamento da aplicação em produção \cite{Fowler_2013}. O elemento central dessa camada costuma ser o sistema de gerenciamento de banco de dados (SGBD), responsável por garantir a integridade, consistência e durabilidade dos dados armazenados. No caso do Nexo, essa função é cumprida pelo PostgreSQL, um SGBD relacional, acessado através do Prisma, um \textit{Object-Relational Mapper} (ORM) que traduz operações do banco de dados em chamadas de código TypeScript, eliminando a necessidade de escrever consultas SQL manualmente na maior parte dos casos. Complementarmente, o Nexo utiliza o Valkey como armazenamento em memória de alta velocidade, empregado tanto para cache quanto como \textit{broker} de mensagens para o sistema de filas, cujo suas filas são gerenciadas com a biblioteca BullMQ para lidar com os \textit{background jobs} enviados. Os mesmos são responsáveis por processar os indicadores semanais e diários descritos nos capítulos anteriores. Além disso, o Nexo mantém uma pilha de observabilidade dedicada à coleta e à consulta dos \textit{logs} de seus contêineres, descrita a seguir, que complementa a persistência dos dados de domínio. \subsection{PostgreSQL} PostgreSQL é um sistema gerenciador de banco de dados relacional, de código aberto, reconhecido por sua conformidade com o padrão SQL, robustez e suporte a tipos de dados avançados \citep{postgresql:2026}. Sua escolha se justifica pela natureza estruturada e relacional dos dados do domínio da aplicação (registros de humor associados a usuários, horários, gatilhos e categorias), que se beneficia das garantias de integridade referencial e das transações ACID oferecidas por um banco relacional maduro, em detrimento de soluções não relacionais. \subsection{Prisma} Prisma é um \textit{Object-Relational Mapper} (ORM) para o ecossistema Node.js/TypeScript, que gera automaticamente um cliente de acesso ao banco de dados tipado a partir de um esquema declarativo, além de oferecer um sistema de migrações versionadas \citep{prisma:2026}. O Prisma foi escolhido por permitir a manutenção do esquema do banco de dados de forma versionada e sincronizada com o código da aplicação, reduzindo a chance de divergência entre a estrutura do banco e as entidades manipuladas pelo back-end, além de prover checagem de tipos em tempo de compilação para as consultas realizadas. O Código~\ref{lst:prisma-schema} reproduz um trecho representativo desse esquema, o modelo de registro de humor. Além dos campos e de seus tipos, o trecho evidencia a convenção de nomenclatura adotada: modelos e campos seguem o padrão do TypeScript --- \texttt{PascalCase} para tipos e \texttt{camelCase} para propriedades ---, enquanto as tabelas e colunas no PostgreSQL usam \texttt{snake\_case}, com nomes de tabela no plural. Os atributos \texttt{@map} e \texttt{@@map} fazem essa tradução, de modo que o código da aplicação manipula \texttt{selectedMood} ou \texttt{anxietyLevel} sem que os nomes físicos das colunas vazem para a API. O trecho também mostra a declaração explícita das relações e o uso de enumerações para valores fechados. Esses \textit{enums} não se limitam ao código: na migração, o Prisma os materializa como tipos enumerados nativos do PostgreSQL, de modo que a restrição aos valores previstos é imposta também pelo banco. O esquema declara enumerações para humores, componentes e intensidade, categorias de gatilho, tipos de ação de cuidado e periodicidade de medicamentos, entre outras. A partir desse único arquivo, o Prisma gera o cliente tipado consumido pela API e as migrações SQL versionadas no repositório, de modo que alterações no modelo passam por revisão e são aplicadas de forma reproduzível em qualquer ambiente. \begin{lstlisting}[caption={Trecho do esquema Prisma para o registro de humor}, label={lst:prisma-schema}] model Mood { id Int @id @default(autoincrement()) annotation String? @db.Text moment DateTime @default(now()) selectedMood BaseMoodOption @default(GOOD) @map("selected_mood") anxietyLevel Int @map("anxiety_level") stressLevel Int @map("stress_level") energyLevel Int @map("energy_level") userId Int @map("user_id") user User @relation(fields: [userId], references: [id]) moodComponents MoodComponent[] triggerLinks TriggerMoodLink[] careActions CareAction[] @@map("moods") } \end{lstlisting} \sourcecode{Elaborado pelo autor (2026)} \subsection{VictoriaLogs e Vector} Observabilidade, no contexto de sistemas distribuídos, designa a capacidade de compreender o estado interno de uma aplicação a partir dos sinais que ela emite --- em especial \textit{logs}, métricas e rastros ---, o que permite investigar problemas sem conhecer previamente sua causa \citep{opentelemetry:2026}. Entre esses sinais, os \textit{logs} são o mais direto: registros datados de eventos emitidos pelos serviços. No Nexo, eles cobrem tanto as requisições HTTP recebidas pela API quanto as tarefas processadas pelos \textit{workers} de segundo plano. A pilha descrita a seguir é dedicada a coletá-los, armazená-los e torná-los consultáveis. VictoriaLogs é um sistema de armazenamento e consulta de \textit{logs} desenvolvido pela VictoriaMetrics, projetado para operar como um único binário, sem dependências externas, e com consumo reduzido de memória e de disco \citep{victorialogs:2026}. Vector é um agente de coleta e roteamento de observabilidade, também distribuído como um único binário, capaz de ler \textit{logs} de diversas fontes --- arquivos, soquetes ou a API do Docker ---, transformá-los por meio de uma linguagem própria de remapeamento e enviá-los a diferentes destinos \citep{vector:2026}. A motivação para adotar essa combinação, em vez de uma pilha bastante difundida como Elasticsearch, Logstash e Kibana (ELK) \citep{elastic:2026}, é proporcional ao porte do projeto: o ELK exige múltiplos serviços, e o Elasticsearch é executado sobre a máquina virtual Java; segundo a documentação do VictoriaLogs, soluções como ele demandam consideravelmente mais memória e disco que o VictoriaLogs \citep{victorialogs:2026}. Em um servidor único que também hospeda a API, o PostgreSQL e o Valkey, esse custo seria desproporcional. VictoriaLogs e Vector cumprem o mesmo papel com dois contêineres menores/mais leves. No Nexo, o fluxo de \textit{logs} começa na própria API, que utiliza a biblioteca Pino para emitir um objeto JSON por linha, em vez de texto livre \citep{pino:2026}. Como o \textit{logger} só aplica formatação legível quando a saída é um terminal interativo, no contêiner --- onde a saída é um \textit{pipe} --- os registros permanecem em JSON de linha única. O \textit{middleware} \texttt{pino-http} associa a cada requisição um identificador (\texttt{X-Request-Id}), convenção difundida para correlacionar os registros de uma mesma requisição \citep{heroku:2026}, reaproveitando o cabeçalho homônimo quando enviado pelo cliente ou gerando um UUID. O Vector lê essas linhas por meio da API do Docker, cujo \textit{socket} é montado no contêiner como volume somente leitura, e aplica uma transformação de remapeamento que tenta interpretar cada mensagem como JSON; quando a interpretação é bem-sucedida, os campos do objeto são elevados ao nível superior do evento, o que torna consultáveis atributos como o nível do \textit{log}, a mensagem e o identificador do usuário. O resultado é enviado em lote ao \textit{endpoint} de ingestão compatível com a API do Elasticsearch do VictoriaLogs, conforme o Código~\ref{lst:vector-pipeline}. \begin{lstlisting}[caption={Configuração do \textit{pipeline} de \textit{logs} no \texttt{vector.yaml}}, label={lst:vector-pipeline}] sources: docker: type: docker_logs transforms: parse_json_message: type: remap inputs: [docker] source: | parsed, err = parse_json(.message) if err == null && is_object(parsed) { . = merge(., parsed) ?? . } sinks: victorialogs: type: elasticsearch inputs: [parse_json_message] endpoints: ["http://victorialogs:9428/logs/insert/elasticsearch/"] mode: bulk api_version: v8 compression: gzip request: headers: VL-Stream-Fields: label.com.docker.compose.service VL-Time-Field: timestamp VL-Msg-Field: msg,message,_msg \end{lstlisting} \sourcecode{Elaborado pelo autor (2026)} O contêiner do VictoriaLogs é configurado com um período de retenção de 14 dias e um teto de 2\,GiB de uso de disco; ambos são limites deliberados para que o armazenamento de \textit{logs} não comprometa o disco da máquina de produção, descartando os registros mais antigos quando qualquer um deles é atingido. O serviço escuta apenas na interface de \textit{loopback} (\texttt{127.0.0.1:9428}), e não em todas as interfaces, de modo que a interface de consulta não é exposta diretamente à internet. O acesso externo se dá pelo Nginx, que publica o caminho \texttt{/logs/} sob autenticação básica HTTP, com credenciais definidas no \textit{vault} criptografado do Ansible; a diretiva \texttt{-http.pathPrefix=/logs} no VictoriaLogs garante que os caminhos gerados pela interface correspondam ao prefixo publicado pelo \textit{proxy}, conforme ilustrado na Figura~\ref{fig:screenshot-victorialogs}. \begin{figure}[H] \centering \caption{Interface de consulta do VictoriaLogs com os \textit{logs} da API} \label{fig:screenshot-victorialogs} \includegraphics[width=\linewidth]{screenshot-victorialogs.png} \source{Elaborado pelo autor (2026)} \end{figure} Na captura, a consulta está restrita ao fluxo de \textit{logs} da API, por meio do filtro \texttt{label.com.docker.compose.service="api"}. À esquerda, o painel de \textit{stream fields} lista os fluxos disponíveis e o volume de registros de cada um; na parte superior, um histograma mostra a distribuição desses registros em intervalos de 30 minutos ao longo do período selecionado; e, na parte inferior, a tabela exibe as entradas correspondentes, com data, nível e mensagem, incluindo desde a sequência de inicialização da API e de seus \textit{workers} até o envio de notificações. Consultas como essa permitem, por exemplo, correlacionar as linhas de uma mesma requisição a partir do seu \texttt{X-Request-Id}. Como o Vector não substitui o \textit{driver} de \textit{logs} do Docker, o \textit{driver} \texttt{json-file} permanece ativo, porém limitado por rotação (10\,MB por arquivo, 3 arquivos por contêiner), de modo que \texttt{docker compose logs} continue funcionando sem que o disco do servidor cresça indefinidamente. As versões das imagens, a retenção e os limites de disco são declarados como variáveis no inventário do Ansible e renderizados no arquivo de ambiente, e o arquivo de configuração do Vector é copiado para o diretório da aplicação pela \textit{role} \texttt{app}, o que mantém a pilha de observabilidade reproduzível pelo mesmo processo de provisionamento descrito na Seção~\ref{sec:deploy-and-ci}. \section{Implantação e Desenvolvimento Contínuo} \label{sec:deploy-and-ci} Já os termos \textit{implantação} (\textit{deployment}) e \textit{automatização} referem-se ao conjunto de práticas e ferramentas utilizadas para transformar o código-fonte de uma aplicação em um sistema efetivamente disponível e em execução, de forma repetível e com o mínimo de intervenção manual \cite{Humble_Farley_2011}. No caso do Nexo, essa camada é sustentada por três ferramentas complementares. O Docker é utilizado para empacotar a aplicação e suas dependências em contêineres isolados e portáveis, garantindo que o ambiente de execução seja idêntico entre desenvolvimento e produção; o Docker Compose orquestra a execução conjunta de múltiplos contêineres (aplicação, banco de dados, Valkey), definindo suas configurações e interdependências em um único arquivo declarativo. O GitHub Actions é responsável pela integração e entrega contínuas (CI/CD), automatizando etapas como execução de testes e construção de imagens a cada alteração no código. Por fim, o Ansible é utilizado para o provisionamento inicial da infraestrutura, automatizando a configuração do servidor onde a aplicação é hospedada. \subsection{Docker e Docker Compose} Docker é uma plataforma de conteinerização que permite empacotar uma aplicação e suas dependências em uma unidade isolada e portátil, capaz de ser executada de forma consistente em diferentes ambientes \citep{docker:2026}. O Docker Compose é uma ferramenta complementar que permite definir e orquestrar múltiplos contêineres --- e as relações entre eles --- por meio de um único arquivo de configuração declarativo \citep{dockercompose:2026}. No projeto, o Docker Compose é utilizado tanto no ambiente de desenvolvimento quanto no ambiente de produção para orquestrar os serviços de banco de dados (PostgreSQL), armazenamento em memória (Valkey) e da própria API, garantindo que as versões e configurações desses serviços sejam idênticas entre as máquinas de desenvolvimento e o servidor de produção. O uso de \textit{healthchecks} em conjunto com a diretiva \texttt{depends\_on} e a condição \texttt{service\_healthy} assegura que a API só seja iniciada após o banco de dados e o Valkey estarem de fato prontos para aceitar conexões, evitando falhas de inicialização por condição de corrida (\textit{race condition}), como ilustrado no Código~\ref{lst:healthcheck-compose}. \begin{lstlisting}[caption={Configuração do \textit{healthcheck} do Valkey e de dependência condicional da API no \texttt{docker-compose.yml}}, label={lst:healthcheck-compose}] valkey: image: valkey/valkey:9.0-alpine healthcheck: test: ["CMD", "valkey-cli", "ping"] interval: 10s timeout: 5s retries: 5 start_period: 5s api: build: ./api depends_on: valkey: condition: service_healthy db: condition: service_healthy \end{lstlisting} \sourcecode{Elaborado pelo autor (2026)} A adoção do Docker se justifica, no contexto de uma aplicação de saúde mental de código aberto, pelo objetivo de garantir a reprodutibilidade do ambiente de execução: qualquer pessoa que clone o repositório --- seja para contribuir com o projeto, auditar seu funcionamento ou realizar sua própria implantação --- consegue reproduzir o ambiente completo sem depender de configurações manuais específicas do sistema operacional hospedeiro, o que reforça o caráter \textit{self-service} do projeto. \subsection{GitHub Actions} GitHub Actions é uma plataforma de integração e entrega contínuas (CI/CD) nativa do GitHub, que permite a definição de fluxos de trabalho automatizados --- disparados por eventos como \textit{push} ou \textit{pull request} --- descritos de forma declarativa em arquivos YAML \citep{githubactions:2026}. No projeto, o fluxo de trabalho de CI/CD é dividido em três etapas sequenciais e dependentes entre si, como visto na Figura~\ref{fig:screenshot-github-actions}. A primeira etapa (\texttt{test}) sobe um serviço efêmero de PostgreSQL e executa a suíte de testes automatizados da API a cada \textit{push} ou \textit{pull request} que modifique arquivos do diretório da API. A segunda etapa (\texttt{build-and-push}), condicionada ao sucesso da primeira e restrita a eventos de \textit{push}, constrói a imagem Docker de produção da API e a publica no GitHub Container Registry (GHCR), com marcação automática pelo SHA do \textit{commit} e, condicionalmente, pela tag \texttt{latest} quando o \textit{push} ocorre na \textit{branch} \texttt{master}. Por último, a terceira etapa (\texttt{deploy-production}), restrita à \textit{branch} \texttt{master}, conecta-se ao servidor de produção via SSH e realiza a atualização do serviço da API por meio da atualização da tag da imagem no arquivo \texttt{.env} e da recriação do contêiner correspondente via Docker Compose. \begin{figure}[H] \centering \caption{Github Actions rodando os testes e realizando \textit{deploy}} \label{fig:screenshot-github-actions} \includegraphics[width=\linewidth]{screenshot-github-actions.png} \source{Elaborado pelo autor (2026)} \end{figure} Esse processo elimina a necessidade de intervenção manual para a maior parte do ciclo de implantação, restringindo a ação humana direta sobre o servidor de produção a situações excepcionais, o que reduz a superfície de erro humano e aumenta a confiabilidade do processo de entrega de novas versões da aplicação. \subsection{Ansible} Ansible é uma ferramenta de automação de infraestrutura que permite descrever, de forma declarativa e idempotente, o estado desejado de um ou mais servidores, dispensando a necessidade de um agente instalado na máquina gerenciada, uma vez que se comunica com ela via SSH \citep{ansible:2026}. No escopo deste projeto, o papel do Ansible é ortogonal ao do Docker Compose e ao do GitHub Actions: enquanto estes automatizam, respectivamente, a orquestração dos serviços da aplicação e o ciclo de integração e entrega contínuas, o Ansible é responsável exclusivamente pelo provisionamento inicial do servidor de produção, executado uma única vez --- ou sempre que um novo servidor precisar ser provisionado. Esse provisionamento consiste em: (1) criar o usuário dedicado à aplicação; (2) adicionar a chave pública SSH utilizada pelo GitHub Actions para se autenticar no servidor durante a etapa de implantação; (3) criar os diretórios da aplicação com as permissões adequadas; e (4) instalar o Docker no servidor. A cópia do arquivo de variáveis de ambiente de produção (\texttt{.env.production}), por conter segredos e credenciais sensíveis, é deliberadamente mantida como uma etapa manual, não automatizada, e não é versionada no repositório. O Ansible, trabalha de forma modularizada, portanto permite que possamos organizar as tarefas a serem feitas em ``perfis'', da qual podem ter uma serie de tarefas para serem executadas em sequencia, \textit{templates}, etc. O Código~\ref{lst:ansible-setup} demonstra a lista de tarefas iniciais, em qual máquinas devem rodar, através da diretiva \textit{hosts}, o método de troca de permissão (através da diretiva \textit{become}) e tarefas posteriores. \begin{lstlisting}[caption={Provisionamento utilizando um \textit{playbook} de configuração}, label={lst:ansible-setup}] - name: Base host setup hosts: vps become: yes become_method: doas roles: - bootstrap - docker - appuser post_tasks: - name: Confirm deploy user can reach Docker ansible.builtin.command: docker info become_user: "{{ deploy_user }}" changed_when: false register: docker_check - name: Show Docker status ansible.builtin.debug: msg: "Docker is reachable by {{ deploy_user }}" when: docker_check.rc == 0 \end{lstlisting} \sourcecode{Elaborado pelo autor (2026)} O Código~\ref{lst:ansible-setup-tasks} mostra o conteúdo de um desses papéis, o de Docker. Cada tarefa descreve um estado desejado --- o pacote instalado, o diretório existente com determinada permissão, o arquivo de configuração do \textit{daemon} com seu conteúdo, o serviço habilitado --- em vez de uma sequência de comandos imperativos. É essa forma declarativa que permite ao Ansible inspecionar o estado atual e agir apenas sobre o que diverge; a tarefa de rotação de \textit{logs}, por exemplo, vale para todos os contêineres, e não apenas para aqueles que declaram um bloco de \textit{logging} próprio no arquivo de composição. \begin{lstlisting}[caption={Tarefas de provisionamento utilizando um \textit{playbook}}, label={lst:ansible-setup-tasks}] - name: Install Docker and Compose plugin community.general.apk: name: - docker - docker-cli-compose state: present - name: Ensure /etc/docker exists ansible.builtin.file: path: /etc/docker state: directory owner: root group: root mode: "0755" # Bound every container's json-file logs globally, not just the ones that set # an explicit logging block in a compose file - name: Configure the Docker daemon log rotation ansible.builtin.copy: dest: /etc/docker/daemon.json content: | { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } } owner: root group: root mode: "0644" notify: restart docker - name: Enable Docker service ansible.builtin.service: name: docker enabled: yes state: started \end{lstlisting} \sourcecode{Elaborado pelo autor (2026)} Automatizar esse provisionamento com o Ansible, em vez de repetir os mesmos passos manualmente por SSH, não economiza apenas comandos: troca um procedimento implícito, que existiria apenas na memória de quem o executa, por um artefato versionado e revisável. Por ser idempotente, o \textit{playbook} pode ser reexecutado após uma falha parcial ou sobre um servidor já configurado sem duplicar usuários, diretórios ou configurações; como consequência, o processo se torna auditável e reproduzível por terceiros. Em um projeto de código aberto, é isso que permite que outra pessoa reconstrua a infraestrutura do zero --- ou a audite antes de confiar nela --- sem acesso prévio ao servidor de produção, além de reduzir a chance de erro humano e de divergência entre ambientes. A divisão em papéis (\textit{roles}) reforça essa propriedade ao separar responsabilidades --- usuário da aplicação, Docker e configuração base ---, de modo que cada parte pode ser alterada e testada isoladamente. A separação entre o provisionamento inicial (\textit{Ansible}), a orquestração de execução (\textit{Docker Compose}) e a automação do ciclo de entrega (\textit{GitHub Actions}) reflete uma decisão arquitetural: cada ferramenta atua em uma camada distinta e bem delimitada do processo de implantação, o que favorece a reprodutibilidade de todo o processo, do zero, por qualquer pessoa que deseje hospedar sua própria instância da aplicação. Essa preocupação é particularmente relevante no contexto de um aplicativo de saúde mental de código aberto: ao reduzir a dependência de serviços proprietários de implantação (\textit{walled gardens}) e ao documentar e automatizar o processo de provisionamento, o projeto favorece um modelo mais alinhado aos princípios de privacidade e de autonomia sobre os dados dos usuários, permitindo que instituições ou indivíduos preocupados com a confidencialidade de dados sensíveis de saúde mental hospedem e controlem sua própria instância da aplicação.