<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: kolaente</title>
    <description>The latest articles on DEV Community by kolaente (@kolaente).</description>
    <link>https://dev.to/kolaente</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F83031%2F657b12a5-ced5-4a7f-b1d3-6dd9f0a3160a.jpeg</url>
      <title>DEV Community: kolaente</title>
      <link>https://dev.to/kolaente</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kolaente"/>
    <language>en</language>
    <item>
      <title>Preview environments with Gitea, Drone and Netlify</title>
      <dc:creator>kolaente</dc:creator>
      <pubDate>Sun, 14 Nov 2021 14:49:46 +0000</pubDate>
      <link>https://dev.to/kolaente/preview-environments-with-gitea-drone-and-netlify-4k57</link>
      <guid>https://dev.to/kolaente/preview-environments-with-gitea-drone-and-netlify-4k57</guid>
      <description>&lt;p&gt;For the &lt;a href="https://code.vikunja.io/frontend"&gt;Vikunja frontend&lt;/a&gt; I wanted to have a preview link with the changes from a PR, right under that PR.&lt;br&gt;
As a reviewer, that makes it a lot easier to see if the changes someone proposed actually work.&lt;/p&gt;

&lt;p&gt;Vikunja's frontend is a classic Vue 3 SPA where you get a js bundle at the end.&lt;/p&gt;

&lt;p&gt;Gitlab has a feature called &lt;a href="https://docs.gitlab.com/ee/ci/review_apps/"&gt;Review Apps&lt;/a&gt; where you basically define a &lt;br&gt;
pipeline stage (using gitlab CI, of course) that deploys the app somewhere and sets a dynamic url. They then pick up that&lt;br&gt;
url from the pipeline and give you a nice button to click on right in the PR.&lt;/p&gt;

&lt;p&gt;Gitea does not have that, so I went and put it together myself.&lt;/p&gt;
&lt;h2&gt;
  
  
  Drone
&lt;/h2&gt;

&lt;p&gt;We're already using &lt;a href="https://drone.io"&gt;Drone CI&lt;/a&gt; for a bunch of things like building, testing, linting and releasing.&lt;br&gt;
For each PR Drone runs a few pipelines to give us quick feedback about the impact of the changes.&lt;br&gt;
In that pipeline, a pipeline builds the frontend. Since the frontend is just a classic SPA, we get a bundle of css, js, images &lt;br&gt;
and so forth at the end of that build. &lt;/p&gt;

&lt;p&gt;Surely we can just throw that on some host, make it available under a dynamic url and call it a day?&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://www.netlify.com/"&gt;Netlify&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Netlify
&lt;/h2&gt;

&lt;p&gt;Netlify is a service that lets you host static content (like an SPA), served with a global CDN.&lt;br&gt;
They have a premium offering with a few additional features, but the free offering works great.&lt;/p&gt;

&lt;p&gt;Their main selling point is automation: Just push to your git repo, and they will build and deploy your site.&lt;/p&gt;

&lt;p&gt;There's &lt;a href="https://docs.netlify.com/configure-builds/get-started/"&gt;integrations for the common git services&lt;/a&gt; like GitHub, Gitlab etc.&lt;br&gt;
No option for Gitea though.&lt;/p&gt;

&lt;p&gt;What makes this really work in Vikunja's case is the &lt;a href="https://docs.netlify.com/cli/get-started/#manual-deploys"&gt;manual option&lt;/a&gt;&lt;br&gt;
which only deploys something already built and does not hand over the build process to Netlify. That allows us to use &lt;br&gt;
the bundle already built by the Drone pipeline.&lt;/p&gt;
&lt;h3&gt;
  
  
  Deploy Previews
&lt;/h3&gt;

&lt;p&gt;Netlify has a feature called "Deploy Previews" where they deploy your site under a vanity url for you to view the changes.&lt;br&gt;
You can use this manually by running &lt;code&gt;netlify deploy&lt;/code&gt; in your source code repo, it will deploy the code to a preview url by default.&lt;/p&gt;

&lt;p&gt;The GitHub integration offers &lt;a href="https://docs.netlify.com/site-deploys/deploy-previews/#app"&gt;deploy previews per PR&lt;/a&gt; which &lt;br&gt;
is pretty much what we need.&lt;/p&gt;

&lt;p&gt;We're going to replicate that on our self-hosted Gitea instance.&lt;/p&gt;
&lt;h3&gt;
  
  
  Netlify setup
&lt;/h3&gt;

&lt;p&gt;To get started with Netlify, you need to tell it a few things. To do that, I created a file called &lt;code&gt;netlify.toml&lt;/code&gt; with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[build]&lt;/span&gt;
  &lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"yarn build"&lt;/span&gt;
  &lt;span class="py"&gt;publish&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dist"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Netlify to deploy the contents of the &lt;code&gt;dist&lt;/code&gt; folder whenever running &lt;code&gt;netlify deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We'll use this to deploy the site as part of our build pipeline.&lt;/p&gt;

&lt;p&gt;To check if everything works, run &lt;code&gt;netlify deploy&lt;/code&gt; in your repo. You'll likely have to set up the connection to your &lt;br&gt;
Netlify account.&lt;/p&gt;
&lt;h2&gt;
  
  
  Automatically deploying changes from a Drone build step
&lt;/h2&gt;

&lt;p&gt;To automatically deploy the code from drone, I added this pipeline step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-preview&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node:16&lt;/span&gt;
  &lt;span class="na"&gt;pull&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;from_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;netlify_auth_token&lt;/span&gt;
    &lt;span class="na"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;from_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;netlify_site_id&lt;/span&gt;
    &lt;span class="na"&gt;GITEA_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;from_secret&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitea_token&lt;/span&gt;
  &lt;span class="na"&gt;commands&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;node ./scripts/deploy-preview-netlify.js&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;build-prod&lt;/span&gt;
  &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going over it step by step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Running in a node 16 container.&lt;/li&gt;
&lt;li&gt;All secrets are passed in via environment variables coming from &lt;a href="https://docs.drone.io/secret/repository/"&gt;Drone secrets&lt;/a&gt;. You need the following:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;netlify_auth_token&lt;/code&gt; - You need to create a new personal access token at &lt;a href="https://app.netlify.com/user/applications"&gt;the user settings page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;netlify_site_id&lt;/code&gt; - The ID of the site you're creating preview deployments for. You can get that by running &lt;code&gt;netlify sites:list&lt;/code&gt; in your repo.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gitea_token&lt;/code&gt; - Another personal access token of a Gitea user, required to write a comment with the preview url under the PR. I recommend creating a bot account for this.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Depends on the &lt;code&gt;build-prod&lt;/code&gt; step which builds a production bundle of Vikunja's frontend.&lt;/li&gt;
&lt;li&gt;The actual pipeline step just runs a node script. We'll get to the contents of that in a minute.&lt;/li&gt;
&lt;li&gt;To make sure it will only create a deployment preview for pull requests, I added a &lt;code&gt;when&lt;/code&gt; trigger to do just that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The script
&lt;/h3&gt;

&lt;p&gt;As of now, this is our script to deploy the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slugify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;slugify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NETLIFY_SITE_ID&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;branchSlug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DRONE_SOURCE_BRANCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DRONE_PULL_REQUEST&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;branchSlug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promiseExec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;promiseExec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./node_modules/.bin/netlify link --id &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;siteId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;promiseExec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`./node_modules/.bin/netlify deploy --alias &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does a few things:&lt;/p&gt;

&lt;p&gt;First, it creates an alias for the deployment. This gives us a nice url like &lt;code&gt;123-branch-name--vikunja-preview.netlify.app&lt;/code&gt;.&lt;br&gt;
By default, Netlify creates a deployment based on some content hash. Using the alias lets us create an url that does &lt;br&gt;
not change per PR. We're creating a slug from the branch name using &lt;a href="https://www.npmjs.com/package/slugify"&gt;slugify&lt;/a&gt; to &lt;br&gt;
make sure the branch name actually works as a subdomain name.&lt;/p&gt;

&lt;p&gt;PR number and branch name are automatically populated by Drone as env variables which we can access with node's &lt;br&gt;
&lt;code&gt;process.env.VARIABLE_NAME&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the alias is done, we need to tell Netlify what site we want to deploy. That's done using the &lt;code&gt;netlify link --id {id}&lt;/code&gt;&lt;br&gt;
command, passing in the site ID as a parameter from env (remember the settings in the drone step?).&lt;/p&gt;

&lt;p&gt;The last thing to do is to actually deploy the content with the &lt;code&gt;netlify deploy --alias {alias}&lt;/code&gt; command, passing the &lt;br&gt;
alias we created earlier as a parameter.&lt;/p&gt;

&lt;p&gt;If all works as expected, we now have preview deployments on Netlify with a nice url!&lt;/p&gt;
&lt;h2&gt;
  
  
  URL comment
&lt;/h2&gt;

&lt;p&gt;While Gitea shows you the pipeline status in the PR and lets you view the pipeline output with a single click, opening the&lt;br&gt;
pipeline and searching for the preview url in the step output is not exactly great UX. What we want is a bot that creates&lt;br&gt;
a comment with the url of the deployment under the PR.&lt;/p&gt;

&lt;p&gt;To do that, we extend the script from earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// We need the user id of the bot to prevent multiple comments as the PR gets updated&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;BOT_USER_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt; 
&lt;span class="c1"&gt;// The gitea token from the pipeline settings&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;giteaToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITEA_TOKEN&lt;/span&gt;
&lt;span class="c1"&gt;// Branch slug, PR number etc. Most of it is already used for the actual deployment&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;branchSlug&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slugify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DRONE_SOURCE_BRANCH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DRONE_PULL_REQUEST&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;alias&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;branchSlug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="c1"&gt;// The second, hard coded part of this url is something you need to set in Netlify's site settings.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fullPreviewUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;--vikunja-frontend-preview.netlify.app`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prIssueCommentsUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://kolaente.dev/api/v1/repos/vikunja/frontend/issues/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/comments`&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="c1"&gt;// Here we get all comments though Gitea's API to see if there's already a comment from our bot&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prIssueCommentsUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasComment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;BOT_USER_ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasComment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`PR #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; already has a comment with a link, not sending another comment.`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Once we arrive here we know for sure there's no other comment yet. That means we can proceed to post one.&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prIssueCommentsUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
Hi &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DRONE_COMMIT_AUTHOR&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!

Thank you for creating a PR!

I've deployed the changes of this PR on a preview environment under this URL: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fullPreviewUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

&amp;gt; Beep boop, I'm a bot.
`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;giteaToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Preview comment sent successfully to PR #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;!`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script does two things: First, it checks whether a comment by the bot already exists to prevent spamming the PR with&lt;br&gt;
comments for every update that gets made to it. If there's no comment yet, we post one with a nice message and the preview url.&lt;br&gt;
You might need to change that if your bot also comments other things, right now we only check based on the user id of the bot.&lt;/p&gt;

&lt;p&gt;We're using the excellent &lt;a href="https://www.npmjs.com/package/axios"&gt;axios&lt;/a&gt; library here to do the http calls to Gitea's API &lt;br&gt;
as &lt;code&gt;fetch&lt;/code&gt; is not available in node js.&lt;/p&gt;

&lt;p&gt;Et voila:&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/preview-deploy-comment.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/preview-deploy-comment.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check out the full script &lt;a href="https://kolaente.dev/vikunja/frontend/src/commit/060057e26834dfeca00a2ba136107e27544e2f61/scripts/deploy-preview-netlify.js"&gt;in the frontend git repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Robots
&lt;/h2&gt;

&lt;p&gt;To prevent crawlers and other robots from accessing and indexing the preview deployments, we add the following to the&lt;br&gt;
&lt;code&gt;netlify.toml&lt;/code&gt; config file in the frontend repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[headers]]&lt;/span&gt;
  &lt;span class="py"&gt;for&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/*"&lt;/span&gt;
  &lt;span class="nn"&gt;[headers.values]&lt;/span&gt;
    &lt;span class="py"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"DENY"&lt;/span&gt;
    &lt;span class="py"&gt;X-XSS-Protection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1; mode=block"&lt;/span&gt;
    &lt;span class="py"&gt;X-Robots-Tag&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"noindex"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets a &lt;code&gt;X-Robots-Tag&lt;/code&gt; header with a value of &lt;code&gt;noindex&lt;/code&gt; for all paths of the site which should prevent bots from &lt;br&gt;
accessing the preview deployments.&lt;/p&gt;
&lt;h2&gt;
  
  
  Rewrites
&lt;/h2&gt;

&lt;p&gt;The Vikunja frontend needs to answer every request and does all routing on the client side. For that to actually work,&lt;br&gt;
you'll need to tell Netlify to redirect every request to the &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;To do this, just add this to your &lt;code&gt;netlify.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[redirects]]&lt;/span&gt;
  &lt;span class="py"&gt;from&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/*"&lt;/span&gt;
  &lt;span class="py"&gt;to&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/index.html"&lt;/span&gt;
  &lt;span class="py"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This was a nice and simple way to get deployment previews on your Gitea instance. Even though Netlify did most of the &lt;br&gt;
heavy lifting (seriously, I've done all of this manually in the past, and it's quite a bit more work), the glue code in &lt;br&gt;
between that adds the comment is what makes this truly amazing in my opinion.&lt;/p&gt;

&lt;p&gt;And it's all automated!&lt;/p&gt;

</description>
      <category>gitea</category>
      <category>drone</category>
      <category>netlify</category>
      <category>preview</category>
    </item>
    <item>
      <title>Opting Out of Google's Federated Learning of Cohorts (FloC) with Traefik 2</title>
      <dc:creator>kolaente</dc:creator>
      <pubDate>Fri, 16 Apr 2021 14:21:29 +0000</pubDate>
      <link>https://dev.to/kolaente/opting-out-of-google-s-federated-learning-of-cohorts-floc-with-traefik-2-49ip</link>
      <guid>https://dev.to/kolaente/opting-out-of-google-s-federated-learning-of-cohorts-floc-with-traefik-2-49ip</guid>
      <description>&lt;p&gt;Google has recently announced it will start tracking the visitors of your website even if you're not using Google Analytics or Adsense.&lt;br&gt;
&lt;a href="https://plausible.io/blog/google-floc"&gt;Plausible&lt;/a&gt; sums it up pretty good, in short:&lt;/p&gt;

&lt;p&gt;They put all chrome users in so called "cohorts" which each represent some group of interest.&lt;br&gt;
Basically, they stop following individuals through the internet but instead just let the chrome browser do the profiling and targeting for them based on the sites they've viewed in the past.&lt;br&gt;
The browser then sends a "cohort" identifier to the websites it visits, telling the website (or rather, the ad network used on it) what group that user is part of to show them more relevant ads.&lt;/p&gt;

&lt;p&gt;While Google uses this move as a privacy friendly manner by banning third-party cookies (which in itself &lt;em&gt;is&lt;/em&gt; a good move) they're essentially abusing their monopoly power as the company building the biggest browser and the biggest ad network.&lt;br&gt;
I'll &lt;a href="https://www.eff.org/deeplinks/2021/03/googles-floc-terrible-idea"&gt;leave it to the eff&lt;/a&gt; to explain in detail why this is such a bad idea.&lt;/p&gt;
&lt;h2&gt;
  
  
  Opting out of FloC as a website owner
&lt;/h2&gt;

&lt;p&gt;FloC is opt-out which means as a website owner, you will need to do something to avoid having your website and its visitors contribute to cohorts rather than opt-in where you would need to include a google script or something like that.&lt;br&gt;
You can do so by sending a &lt;code&gt;Permissions-Policy&lt;/code&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Permissions-Policy: interest-cohort=()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Traefik has &lt;a href="https://doc.traefik.io/traefik/middlewares/headers/#adding-headers-to-the-request-and-the-response"&gt;a middleware&lt;/a&gt; to add custom headers which I'll use to send the &lt;code&gt;Permissions-Policy&lt;/code&gt; header to the visitors of my site.&lt;br&gt;
To do that, we'll have to create a middleware with the header first.&lt;br&gt;
Pretty straight forward with traefik, simply create a new config file with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;middlewares&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nofloc&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;customResponseHeaders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Permissions-Policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;interest-cohort=()"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(this is using yaml, but it will ofc work with toml just fine)&lt;/p&gt;

&lt;p&gt;I like adding these kind of general things to &lt;a href="https://doc.traefik.io/traefik/providers/file/"&gt;config files&lt;/a&gt; so I can use them globally and won't have to recreate them for each container configuration I use.&lt;/p&gt;

&lt;p&gt;Now we can add the &lt;code&gt;nofloc@file&lt;/code&gt; middleware to any traefik router.&lt;br&gt;
If you're using traefik to expose docker containers to the internet, it would look something like this in a docker-compose file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;whoami&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik/whoami&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.whoami.middlewares=nofloc@file"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Applying it to all services
&lt;/h2&gt;

&lt;p&gt;If you're like me, you're probably hosting quite a few services.&lt;br&gt;
While you could just add the middleware by hand to all of these it is a lot easier (and faster) to just add it globally.&lt;br&gt;
Traefik lets you &lt;a href="https://doc.traefik.io/traefik/routing/entrypoints/#middlewares"&gt;add middlewares to http entrypoints&lt;/a&gt; which will basically add them to all services on that entrypoint.&lt;br&gt;
The configuration for our &lt;code&gt;nofloc&lt;/code&gt; middleware is pretty straighforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;entryPoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;:443&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;middlewares&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nofloc@file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While changes to files in middlewares are automatically picked up by traefik and don't require you to restart it you will need to restart traefik every time you change the configuration of the entrypoints.&lt;/p&gt;

&lt;p&gt;After doing that, all services using the &lt;code&gt;https&lt;/code&gt; entrypoint will send the &lt;code&gt;Permissions-Policy&lt;/code&gt; header (this blog being one of them).&lt;/p&gt;

&lt;p&gt;You can verify this with curl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl -I https://blog.kolaente.de/2021/04/opting-out-of-googles-federated-learning-of-cohorts-floc-with-traefik-2/
HTTP/2 200 
accept-ranges: bytes
cache-control: no-cache
content-type: text/html; charset=utf-8
date: Fri, 16 Apr 2021 09:55:58 GMT
etag: "60795dff-26bb"
expires: Thu, 01 Jan 1970 00:00:01 GMT
last-modified: Fri, 16 Apr 2021 09:50:55 GMT
permissions-policy: interest-cohort=()
server: nginx/1.19.10
vary: Accept-Encoding
content-length: 9915
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A service &lt;a href="https://securityheaders.com/"&gt;like this one&lt;/a&gt; will also work just fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;While Killing third-party cookies is great (and browsers like Safari on MacOS have already started doing this) Google abusing its monopoly power to force FloC onto every website user and owner is not.&lt;br&gt;
As someone who does not use any Google services, not on my websites nor in my every day usage of the internet, I am not a fan of having to take action on my sites to opt out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vikunja.cloud/?utm_source=kolanete_blog&amp;amp;utm_medium=post&amp;amp;utm_campaign=floc_opt_out_traefik"&gt;Privacy matters&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Any questions or suggestions? &lt;a href="https://twitter.com/kolaente"&gt;Hit me up on twitter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Ressources
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://paramdeo.com/blog/opting-your-website-out-of-googles-floc-network"&gt;Paramdeo Singh&lt;/a&gt; has done a good way to explain how to opt out of FloC when you're using other setups like nginx, apache, netliy etc. over on his blog.&lt;/p&gt;

</description>
      <category>google</category>
      <category>traefik</category>
      <category>floc</category>
      <category>advertising</category>
    </item>
  </channel>
</rss>
