[{"content":"Last updated: November 2, 2026. Translation of the Portuguese policy.\n1. Overview zocate.li is a personal information technology blog maintained by Lincoln Zocateli. This policy describes the data collected, how it is used and your rights concerning that information.\nBy using this site, whether reading articles, commenting, subscribing to the newsletter or using social login, you agree to the practices described in this policy.\n2. Data collected 2.1. Comments (Comentario) Comments are provided by Comentario, self-hosted open-source software. When you comment, the following data is collected:\nName and email, entered manually or obtained through social login. Comment text. IP address, for moderation and spam prevention. Publication date and time. Comment data is stored in a PostgreSQL database on the blog\u0026rsquo;s own VPS infrastructure. No data is shared with third parties for commenting purposes.\n2.2. Social login (OAuth) The blog offers external authentication providers for commenting:\nProvider Data obtained GitHub Name, email, avatar GitLab Name, email, avatar Google Name, email, avatar Facebook Name, email, avatar Twitter / X Name, handle, avatar Social login verifies your identity but does not give this blog access to your feed, messages, contacts or information beyond your basic public profile. Provider credentials are managed by the respective platforms; the blog never stores your password.\n2.3. Newsletter (Listmonk) The newsletter uses self-hosted open-source Listmonk. Your email and optional name are stored in the blog database. You receive emails about news and new articles. No email address is shared with third-party marketing services. Unsubscribe using the link in any newsletter email.\n2.4. Cookies and local storage The blog uses cookies and localStorage minimally:\nPurpose Type Description Comment session Cookie Keeps your Comentario session authenticated Theme preference localStorage Remembers Light, Dark or System (zocateli.theme); imports the previous pref-theme choice Language preference localStorage Remembers an explicit language choice (zocateli.language); automatic browser detection is not saved as a manual choice Consent preference localStorage Remembers your Analytics consent choice separately Google Analytics 4 Cookie Measures sessions and pages visited Google AdSense Cookie Displays relevant contextual advertising when enabled Theme and language preferences are stored locally in your browser, not sent as Analytics consent. You can clear them in browser settings or return to automatic language selection through the language menu.\n2.5. Google Analytics 4 and Google AdSense The blog uses Google LLC services. Google Analytics 4 collects traffic statistics such as pages visited, traffic origin, devices and session duration, without direct personal identification by default. Google AdSense, when enabled, uses interest cookies to personalize ads.\nGoogle may use this information according to its Privacy Policy. You can disable ad personalization or use the Google Analytics opt-out add-on.\n2.6. Server logs Standard Nginx access logs include IP address, URL, HTTP method, response status, date and time. They are used exclusively for diagnostics and security. Logs older than 30 days are automatically removed.\n3. How data is used Collected data is used exclusively to publish and manage comments, send newsletters to subscribers, authenticate comment users, diagnose technical issues and prevent abuse.\nWe do not sell, rent or share your data with advertisers or marketing companies.\n4. Storage and security Data is stored on the blog\u0026rsquo;s own cloud VPS infrastructure rather than third-party services. Communication uses HTTPS/TLS through Cloudflare. Database access is restricted and password-protected. Regular backups use strict access controls.\n5. Your rights Under Brazil\u0026rsquo;s General Data Protection Law (LGPD, Law 13,709/2018) and the Meta Platform Terms, you may request access, correction, deletion and portability of your personal data. You may unsubscribe from the newsletter at any time.\nFor deletion, including data from Facebook, Google, GitHub, GitLab or Twitter/X login, see Data Deletion. That page describes processing deadlines and how to revoke provider permissions. For other requests, use the contact information below.\n6. External links The blog may link to external websites. It is not responsible for their privacy practices. Read the privacy policy of each external site you visit.\n7. Contact Site: zocate.li GitHub: lzocateli 8. Policy changes This policy may be updated periodically. Significant changes will be communicated through the newsletter to subscribers. The date at the top identifies the current version.\n","permalink":"https://zocate.li/en/privacy/","summary":"How zocate.li handles your personal data.","title":"Privacy Policy"},{"content":"Translation of the Portuguese terms, last updated February 18, 2026.\n1. Acceptance By accessing zocate.li, reading articles, commenting or subscribing to the newsletter, you agree to these Terms of Use. If you disagree with any part, do not use this site.\n2. About the blog This is a personal technical blog maintained by Lincoln Zocateli, covering software development, DevOps, cloud, containers and related technologies. Content is educational and informational, without affiliation with any company or organization.\n3. Intellectual property All original articles, text, author-created images and code examples belong to Lincoln Zocateli. All rights reserved, unless explicitly stated otherwise.\nYou may read, share and reference articles with attribution and a link to the original. Code snippets may be used in personal or professional projects under the MIT license unless stated otherwise.\nYou may not reproduce complete articles elsewhere without prior written permission, use content to create AI training materials without permission, or remove or alter author attribution.\n4. Comments Keep comments respectful, constructive and related to the article. Do not publish offensive, discriminatory, defamatory or illegal content, spam, advertising or unsolicited links. Do not impersonate others.\nThe author may edit, hide or remove comments that violate these rules without prior notice. Repeated violations may result in blocking the user. Comment opinions are the sole responsibility of their authors; the blog does not endorse them or accept responsibility for third-party comments.\n5. Newsletter The newsletter is an optional notification service. You provide your email voluntarily, may unsubscribe through any newsletter email and your email will not be shared with third parties.\n6. External links External resources, tools and projects are linked for reference. The blog is not responsible for their content, availability or practices.\n7. Disclaimer Content is provided as is for education and information. The author makes reasonable efforts to keep it accurate but does not guarantee that it is error-free or current, nor accept responsibility for problems caused by following published instructions or code.\nAlways review and test configurations or code before applying them in production. You use information from this blog at your own risk.\n8. Availability Maintenance, updates or technical problems may temporarily interrupt service. Continuous availability is not guaranteed.\n9. Applicable law These terms are governed by the laws of the Federative Republic of Brazil, particularly:\nGeneral Data Protection Law (13,709/2018). Internet Civil Framework (12,965/2014). Brazilian Civil Code (10,406/2002). 10. Contact Site: zocate.li GitHub: lzocateli 11. Changes These terms may change at any time. The date identifies the current version. Continued use after changes constitutes acceptance of the new terms.\n","permalink":"https://zocate.li/en/terms/","summary":"Terms for reading and interacting with zocate.li.","title":"Terms of Use"},{"content":"Data deletion instructions If you used Facebook Login or another social provider to comment, you may request deletion of all your personal data at any time.\nData collected through Facebook Login Data Source Purpose Name Facebook profile Display on comments Email Facebook profile Account identification Profile picture URL Facebook profile Display on comments Comments Entered by you Publication Registration date Generated internally Audit and moderation We do not store passwords, access tokens, friends lists, feeds, messages, photos or other Facebook data beyond the basic public profile listed above.\nRequest deletion Email blog@zocate.li, subject Data Deletion Request - zocate.li. Include the name and/or email used to log in and the provider, such as Facebook. Within 5 business days, you will receive a confirmation code, such as DEL-2026-0001, and a status URL to track the request. Data will be permanently deleted within 15 business days of the request. You will receive a final confirmation email. Your comment account, all published comments and locally stored profile data will be removed. Deletion is irreversible. If you prefer account deactivation without removing comments, state this in your request.\nRequest status Reply to the confirmation email with your code, or email blog@zocate.li with subject Deletion Status - [confirmation code]. We will reply within 3 business days.\nRevoke Facebook permissions Open Facebook Settings: Apps and Websites, find blog-prd-comentario, choose Remove and select the option to delete all app data.\nRevoking permissions prevents future logins but does not automatically delete information already stored on this blog. Submit the request described above as well.\nOther login providers Google. GitHub. GitLab. Twitter/X. Newsletter Use the Unsubscribe link in any email or send a request to blog@zocate.li. Cancellation is processed immediately.\nContact Email: blog@zocate.li. Privacy Policy. Terms of Use. ","permalink":"https://zocate.li/en/data-deletion/","summary":"Data deletion instructions and processing deadlines.","title":"User Data Deletion"},{"content":"Who I am My name is Lincoln Zocateli. I am a Solutions Architect with more than 25 years of experience in information technology. Throughout this journey, I have designed, built and evolved systems at different scales, from enterprise applications to distributed cloud platforms.\nWhat I do I focus on designing robust, secure and scalable solutions, combining software architecture with modern infrastructure and continuous delivery practices. My everyday technologies include:\n.NET platform: .NET Core / .NET 8+, C#, RESTful APIs and microservices. Containers and orchestration: Docker, rootless Podman, Kubernetes (AKS), optimized images and containerized workloads. Linux: administration, shell scripting and production environments. DevOps and CI/CD: automated pipelines with GitHub Actions and Azure DevOps, continuous deployment and reliable delivery. Infrastructure as Code: Terraform, Ansible, automated provisioning and configuration. Azure Cloud: PaaS/IaaS, App Services, AKS, Azure Functions, Storage and Service Bus. Security and identity: JWT, OAuth 2.0, OpenID Connect, authentication and authorization in APIs and distributed applications. About the blog This blog is where I share knowledge, practical experience and opinions about software development, DevOps, cloud and solutions architecture. I document what I learn in my daily work and hope it can help other professionals along the way.\nAway from the keyboard I am a proud father of Thaís and love German Shepherd dogs, loyal companions that remind me life extends well beyond code and deployments.\nContact GitHub: lzocateli LinkedIn: lzocateli Email: blog [at] zocate.li ","permalink":"https://zocate.li/en/about/","summary":"\u003ch2 id=\"who-i-am\"\u003eWho I am\u003c/h2\u003e\n\u003cimg src=\"/images/lincoln.jpg\" alt=\"Lincoln Zocateli\" class=\"author-photo\" /\u003e\n\u003cp\u003eMy name is \u003cstrong\u003eLincoln Zocateli\u003c/strong\u003e. I am a \u003cstrong\u003eSolutions Architect\u003c/strong\u003e with more than \u003cstrong\u003e25 years of experience\u003c/strong\u003e in information technology. Throughout this journey, I have designed, built and evolved systems at different scales, from enterprise applications to distributed cloud platforms.\u003c/p\u003e\n\u003ch2 id=\"what-i-do\"\u003eWhat I do\u003c/h2\u003e\n\u003cp\u003eI focus on designing robust, secure and scalable solutions, combining software architecture with modern infrastructure and continuous delivery practices. My everyday technologies include:\u003c/p\u003e","title":"About Lincoln Zocateli, Solutions Architect"},{"content":"Introduction A script that runs successfully in a server terminal can fail in a CI/CD pipeline because the execution environment is different. The agent may use another account, a different shell, a restricted PATH, a separate working directory or different credentials. Successful interactive execution is not proof that the same script will work unattended.\nWhen I investigate this problem, I compare those contexts before changing the script itself. A missing binary, an unexpected directory or an authentication prompt can explain a failure that initially looks mysterious. The goal is to make the required environment explicit rather than depend on the personal configuration of whoever tested the script.\nThis article explores what changes between a Linux server terminal and an Azure DevOps pipeline. It covers users, environment variables, permissions, network access and ephemeral state, followed by examples, a diagnostic checklist and a Bash template. These principles also apply to other CI/CD systems.\nThe intended audience includes developers, DevOps engineers and system administrators who need repeatable automation. Task automation with Makefile provides related background in Portuguese. The practical objective is to reduce assumptions, so scripts work reliably both locally and in a job with no human present to answer questions.\nI do not assume that every pipeline has the same permissions or installed tools. Hosted and self-hosted agents have different contracts. A useful diagnosis identifies the actual account, image and task configuration involved, then checks the dependencies that the script really uses.\nWhat changes between the terminal and a pipeline Aspect Server terminal Pipeline User/session Your interactive account Agent or service account, usually noninteractive Shell User\u0026rsquo;s configured shell and profile Shell invoked by the task; profile loading differs Environment Local variables and customized PATH Controlled job variables and a different PATH Working directory Whatever directory you opened Agent workspace or task-configured directory Permissions Your account\u0026rsquo;s permissions and possibly a TTY Agent permissions; usually no interactive TTY Dependencies Server tools and installed versions Tools available on the selected agent/image Interaction You can answer prompts Prompts can hang or fail Network Server routes, proxy and certificates Agent routes, proxy and egress restrictions Files Persistent server paths Repository workspace and pipeline artifacts Resources Host limits Job timeouts, quotas and concurrency limits Logs Local history, syslog or files Centralized logs, secret masking and retention State Persistent machine state Hosted agents are typically ephemeral Information: Use this table to identify concrete differences. A pipeline is not simply your terminal opened remotely: its account, directory and dependencies must be established separately.\nWhy the execution context matters User and TTY In a terminal, commands run as your user with a terminal device. Tools can detect the TTY and enable prompts, colors or interactive authentication. The pipeline agent normally runs without a person present. A read statement, a password prompt or an authentication wizard can therefore block the job.\nThe agent documentation describes the role of agents in executing jobs. Check the service identity and how that agent starts, rather than assuming it inherits the identity of the person who queued the build.\nEnvironment and PATH Your local profile may add /usr/local/bin, particular Node/.NET/Java versions, aliases and shell functions. None of those conveniences should be assumed in the agent. A tool can be absent, present under a different path or installed at another version. For example, changing the Node version can change dependency behavior even when the source script is identical.\nProfiles also load differently between interactive, login and noninteractive shells. Do not treat .bashrc and .profile as interchangeable or assume that a task imports both. Initialize the required environment explicitly in automation.\nDirectories and relative paths Scripts that interpret . as a particular directory or rely on an earlier cd are fragile. In Azure Pipelines, source files are normally found under $(Build.SourcesDirectory), while artifacts can be written under $(Build.ArtifactStagingDirectory). A task can also configure its working directory.\nWarning: Never assume the current directory matches your terminal. Set the task\u0026rsquo;s working directory and use explicit paths for files that must be found reliably.\nPermissions and security policies Your interactive account may have sudo access or permissions that the agent account lacks. ACLs, SELinux and AppArmor can also affect access. A service account that cannot read a configuration or restart a service does not become authorized merely because the operation worked in your session.\nCheck permissions for the actual agent identity. Do not disable security policies to conceal an environment difference. A hosted build agent and a production server are different machines with different responsibilities.\nDependencies and versions A successful server run may depend on packages installed months earlier. Hosted agents use a predefined image; self-hosted agents depend on how you maintain them. Unless the environments deliberately match, expect differences. Pin critical versions and declare dependencies so the script can fail with a useful message when something is missing.\nNetwork, credentials and state Registries and APIs may require proxy configuration, corporate certificates or permitted outbound routes. Credentials located under a user\u0026rsquo;s home directory, such as ~/.aws/credentials or ~/.npmrc, may not exist for the agent account. Use the pipeline\u0026rsquo;s supported secret mechanisms instead of copying personal credential files.\nEphemeral agents also invalidate assumptions about files left by earlier executions. Use managed artifacts and pipeline caching when the workflow needs data across jobs or runs. A temporary local file is not an artifact contract.\nPractical examples Sudo without an interactive terminal The following command may ask for a password and fail in an unattended job:\n1 sudo systemctl restart myservice The original example authorizes one specific operation for the agent account:\n1 vsts ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart myservice This requires administrator review and is not a reason to give the agent unrestricted sudo access. Host service operations belong on an appropriate self-hosted deployment environment, not on an unrelated hosted build machine.\nA tool available only in your PATH 1 jq \u0026#39;.version\u0026#39; package.json If jq exists only on your server, the agent needs its own declared installation step or a job container that includes it. The source example uses Ubuntu package installation before running the command:\n1 2 3 4 5 6 7 steps: - script: | sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y jq displayName: \u0026#39;Install jq\u0026#39; - script: | jq \u0026#39;.version\u0026#39; package.json displayName: \u0026#39;Run script with jq\u0026#39; Do not apply this installation step indiscriminately to every agent type. It assumes a compatible package manager and the required privilege. A pinned container image is another way to make that dependency reproducible.\nAn invisible prompt 1 read -p \u0026#34;Continue? (y/n) \u0026#34; ans An unattended job cannot answer this question. Use an explicit noninteractive mode and pipeline parameters instead of leaving the prompt in the job. The original example retains an optional terminal prompt:\n1 2 3 4 5 SKIP_CONFIRM=\u0026#34;${SKIP_CONFIRM:-1}\u0026#34; if [[ \u0026#34;$SKIP_CONFIRM\u0026#34; != \u0026#34;1\u0026#34; ]]; then read -p \u0026#34;Continue? (y/n) \u0026#34; ans [[ \u0026#34;$ans\u0026#34; != \u0026#34;y\u0026#34; ]] \u0026amp;\u0026amp; exit 0 fi Tip: Separate interactive convenience from automated execution. An explicit noninteractive mode makes the contract visible, while destructive actions still require appropriate authorization outside the prompt.\nAzure DevOps considerations Hosted agents provide a Microsoft-managed, ephemeral environment with predefined tools. Self-hosted agents give you control over OS, versions and installed dependencies. Choose deliberately: owning the machine can improve parity, but also makes you responsible for maintenance and isolation.\nFor Bash scripts, use Bash@3 and configure workingDirectory when needed. Avoid relying on .bashrc; initialize the required environment within the script or task.\nPass credentials through secret variables or supported service connections. Authentication must be noninteractive and scoped to the operation. Related background on authentication and authorization is available in Portuguese.\nConfigure timeoutInMinutes so a blocked job does not run indefinitely. Use bounded retries with backoff for genuinely transient external failures. Increasing the timeout does not fix a permanent permission failure or a missing dependency.\nDiagnostic checklist Identity: inspect id -u -n, whoami, the shell and whether a TTY exists. Environment: compare PATH and locale; inspect only non-sensitive variables. Do not dump secrets into job logs. Directory: check pwd, file paths and the task\u0026rsquo;s working directory. Tools: use command -v and version commands for critical binaries. Permissions: inspect authorized access for the service account. Do not execute a destructive command merely to test permissions. Network: verify the relevant endpoint, proxy and certificates without printing credentials. Logs: compare application and job errors, not only the final exit status. State: identify files that require persistence, caching or artifacts. A short diagnostic step can reveal a version mismatch before the main work begins. Keep its output scoped: verbose logs and shell tracing can expose credentials even when the pipeline masks some registered secrets.\nTips and good practices Standardize the environment. Use a pinned job container or deliberately maintained agent. The same source code is not enough if its runtime differs. Declare dependencies. Check critical binaries and versions before starting work. Missing tools should produce an actionable failure, not an unrelated error later. Avoid interaction. Use supported noninteractive flags and explicit parameters. Do not leave read, menus or authentication wizards in unattended jobs. Manage credentials securely. Prefer secret variables and service connections. Avoid personal home-directory credential dependencies. Use explicit paths. Set the working directory and resolve inputs against a known location. This removes a common source of terminal-only success. Seek idempotence. Re-running the script should converge rather than break an already-correct state. Check the current state before modifying it. Make failures visible. set -euo pipefail helps surface errors, but does not replace deliberate error handling. Use debug tracing only when it cannot expose sensitive values. Bash template The original article\u0026rsquo;s template illustrates environment normalization, dependency checks and bounded retries. It is a starting point, not a production deployment policy; review permissions, logging and the external endpoint before use.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/usr/bin/env bash set -euo pipefail [[ \u0026#34;${DEBUG:-0}\u0026#34; == \u0026#34;1\u0026#34; ]] \u0026amp;\u0026amp; set -x export PATH=\u0026#34;/usr/local/bin:/usr/bin:/bin:$PATH\u0026#34; WORKDIR=\u0026#34;${WORKDIR:-$(pwd)}\u0026#34; cd \u0026#34;$WORKDIR\u0026#34; need() { command -v \u0026#34;$1\u0026#34; \u0026gt;/dev/null 2\u0026gt;\u0026amp;1 || { echo \u0026#34;ERROR: Missing \u0026#39;$1\u0026#39; in PATH. Install it first.\u0026#34; exit 127 } } need jq need awk NON_INTERACTIVE=\u0026#34;${NON_INTERACTIVE:-1}\u0026#34; retry() { local n=0 until \u0026#34;$@\u0026#34;; do n=$((n + 1)) [[ $n -ge 3 ]] \u0026amp;\u0026amp; { echo \u0026#34;ERROR: Failed after 3 attempts: $*\u0026#34;; exit 1; } echo \u0026#34;Attempt $n failed. Waiting $((n * 2))s...\u0026#34; sleep $((n * 2)) done } The source continues by passing an API token to curl. Do not enable shell tracing around secret-bearing calls, and do not include command arguments containing tokens in retry error messages. Those details need hardening before using the template with real credentials.\nObjective Summary Execution context includes the account, shell, working directory, dependencies and network; it can explain terminal/pipeline differences without a script logic bug. Noninteractive jobs cannot reliably answer prompts and require an explicit unattended execution contract. PATH and tool versions must be declared or checked, rather than inherited from personal profiles. Ephemeral agents require managed artifacts or caches for state that must survive a job. Security requires scoped credentials, reviewed permissions and logs that do not expose secrets. Read Also Makefile task automation (Portuguese). Authentication and authorization: JWT, OAuth2 and OpenID Connect (Portuguese). BFF security for SPAs (Portuguese). References What is Azure Pipelines?: the CI/CD service. Azure Pipelines agents: hosted and self-hosted execution. Secret variables: supported secret management. Pipeline caching: managed cache behavior. Bash Reference Manual: shell behavior and error handling. SELinux: mandatory access control on Linux. ","permalink":"https://zocate.li/en/posts/2025/diferenca-executar-script-terminal-pipeline/","summary":"Execution context explains differences between interactive terminals and pipelines: users, permissions, PATH, credentials and ephemeral agents.","title":"Terminal vs Pipeline: differences when running scripts"},{"content":"Projects Nuuvify.CommonPack A collection of open-source .NET libraries for robust, scalable, high-performance applications. The project includes more than 20 NuGet packages covering:\nData persistence: Unit of Work, Repository Pattern, AutoHistory and advanced dynamic filters supporting complex types and nested navigation (Customer.Address.City). Security: JWT authentication with refresh tokens, Azure AD authentication and encryption of sensitive data. Cloud infrastructure: Azure Service Bus, Azure Storage and Background Services integration with advanced diagnostics. Communication: an HTTP client with retries and resilience (Polly), and SMTP email delivery with MailKit. Middleware and APIs: custom ASP.NET Core middleware, OpenAPI/Swagger and Health Checks. Observability: structured logging, custom health checks and distributed tracing. Stack: C#, .NET 8+, Entity Framework Core, SQL Server, PostgreSQL, Oracle, Azure Service Bus and Docker.\nLicense: MIT.\nag-server-grid An open-source Angular data grid with server-side pagination, its main difference from conventional grids. It targets enterprise applications that handle large datasets without loading everything into the client.\nServer-side pagination: consumes a paginated API, tracking the current page, total records and records per page. Integrated CRUD: Edit and Delete buttons with single or multiple row selection (Ctrl+Click and Ctrl+Shift+Click). CSV export: exports visible data to a CSV file. Internationalization: dynamic header translations through ngx-translate. Sorting and resizing: column sorting and width controls (minWidth/maxWidth). Automatic date formatting: detects date fields. Customizable buttons: in-cell buttons with icons, CSS classes and custom events. Local pagination: an alternative for smaller datasets. Testable: unit test examples for table headers and body. Stack: Angular, TypeScript and npm.\nLicense: Apache-2.0.\n","permalink":"https://zocate.li/en/projects/","summary":"\u003ch2 id=\"projects\"\u003eProjects\u003c/h2\u003e\n\u003ch3 id=\"nuuvifycommonpack\"\u003e\u003ca href=\"https://github.com/nuuvify/Nuuvify.CommonPack\"\u003eNuuvify.CommonPack\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eA collection of open-source .NET libraries for robust, scalable, high-performance applications. The project includes more than 20 NuGet packages covering:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eData persistence\u003c/strong\u003e: Unit of Work, Repository Pattern, AutoHistory and advanced dynamic filters supporting complex types and nested navigation (\u003ccode\u003eCustomer.Address.City\u003c/code\u003e).\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSecurity\u003c/strong\u003e: JWT authentication with refresh tokens, Azure AD authentication and encryption of sensitive data.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCloud infrastructure\u003c/strong\u003e: Azure Service Bus, Azure Storage and Background Services integration with advanced diagnostics.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eCommunication\u003c/strong\u003e: an HTTP client with retries and resilience (Polly), and SMTP email delivery with MailKit.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eMiddleware and APIs\u003c/strong\u003e: custom ASP.NET Core middleware, OpenAPI/Swagger and Health Checks.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eObservability\u003c/strong\u003e: structured logging, custom health checks and distributed tracing.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eStack:\u003c/strong\u003e C#, .NET 8+, Entity Framework Core, SQL Server, PostgreSQL, Oracle, Azure Service Bus and Docker.\u003c/p\u003e","title":"Open-Source Projects and Contributions"}]