{"@attributes":{"version":"2.0"},"channel":{"title":"code and society | codedge","link":"https:\/\/www.codedge.de\/","description":"on code and society | codedge","language":"en-us","lastBuildDate":"Wed, 05 Nov 2025 21:58:19 +0100","item":[{"title":"Managing secrets with SOPS in your homelab","link":"https:\/\/www.codedge.de\/posts\/managing-secrets-sops-homelab\/","pubDate":"Thu, 06 Nov 2025 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/managing-secrets-sops-homelab\/","description":"<p>Sealed Secrets, Ansible Vault, 1Password or SOPS - there are multiple ways\nhow and where to store your secrets. I went with SOPS and age\nwith my ArgoCD GitOps environment.<\/p>\n<p>Managing secrets in your homelab, be it within a Kubernetes cluster or while deploying systems and tooling with\nAnsible, is a topic that arises with almost 100% certainty. In general you need to decide, whether you want secrets\nto be held and managed externally or internally.<\/p>\n<ul>\n<li><strong>Externally managed:<\/strong> this includes either a self-hosted and externally hosted secrets solution like AWS KMS, password\nmanager like 1Password or similar<\/li>\n<li><strong>Internally managed:<\/strong> solutions where your secrets live next to your code, no external service is need<\/li>\n<\/ul>\n<p>One important advantage I see with internally managed solutions is, that I do not need an extra service. No extra costs\nand connections, no chicken-egg-problem when hosting your passwords inside your own Kubernetes cluster, but cannot\nreach it when the cluster is down.<\/p>\n<p>Therefore I went with <a href=\"https:\/\/getsops.io\">SOPS<\/a> for both, secrets for my Ansible scripts and secrets I need to set\nfor my K8s cluster. While SOPS can be used with PGP, GnuPG and more, I settled with <a href=\"https:\/\/github.com\/FiloSottile\/age\">age<\/a> as encryption.\nWith SOPS your secrets live, encrypted, inside your repository and can be en-\/decrypted on-the-fly whenever needed.<\/p>\n<p>The private key for encryption should, of course, never be committed into your git repository or made available to\nuntrusted sources.<\/p>\n<h2 id=\"setup-sops--age\" class=\"group\">\n Setup SOPS + age<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#setup-sops--age\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>First, we need to install SOPS, age and generate an age key. SOPS is available for all common operating systems via the\npackage manager. I either use Mac or Arch:<\/p>\n<ul>\n<li>Mac: <code>brew install sops age<\/code><\/li>\n<li>Arch Linux: <code>sudo pacman -S sops age<\/code><\/li>\n<\/ul>\n<p>Now we need to generate an age key and link it to SOPS as the default key to encrypt with.<\/p>\n<p><strong>Generate an age key<\/strong><\/p>\n<p>Our age key will live in <code>~\/.config\/sops\/age\/default.agekey<\/code>.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>mkdir -p ~\/.config\/sops\/age <span style=\"color:#89dceb;font-weight:bold\">&amp;&amp;<\/span> age-keygen &gt; ~\/.config\/sops\/age\/default.agekey\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Now we tell SOPS where to find our age key. I put the next line in my <code>.zshrc<\/code>.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span><span style=\"color:#89dceb\">export<\/span> <span style=\"color:#f5e0dc\">SOPS_AGE_KEY_FILE<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span>~\/.config\/sops\/age\/default.agekey\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>The last thing to do is to put a <code>.sops.yaml<\/code> in your folder from where you want to encrypt your files. This file\nacts as a configuration regarding the age recipient (key) and how the data should be encrypted.<\/p>\n<p>My config file looks like this:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">8\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">creation_rules<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">path_regex<\/span>: k8s\/*\\.sops\\.yaml$\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">encrypted_regex<\/span>: <span style=\"color:#a6e3a1\">&#39;^(data|stringData)$&#39;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">age<\/span>: &gt;-<span style=\"color:#6c7086\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> age15xzkvh5csaxu2w4x280mhwur5m8svynl3l0gdgvax2r3tt4psqpqr623ag<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">path_regexp<\/span>: ansible\/*\\.sops\\.yaml$\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">age<\/span>: &gt;-<span style=\"color:#6c7086\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> age15xzkvh5csaxu2w4x280mhwur5m8svynl3l0gdgvax2r3tt4psqpqr623ag<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><ul>\n<li>Two separate rules depending on the folder, where the encrypted files are located<\/li>\n<li>Files ending with <code>*.sops.yaml<\/code> are targeted<\/li>\n<li>The age key, that should be used for en-\/decryption is specified<\/li>\n<\/ul>\n<p>You might wonder yourself about the first rule with <code>data|stringData<\/code>. I will just quote the [KSOPS docs](To make encrypted secrets more readable, we suggest using the following encryption regex to only encrypt data and stringData values. This leaves non-sensitive fields, like the secret&rsquo;s name, unencrypted and human readable.) here:<\/p>\n<blockquote>\n<p>To make encrypted secrets more readable, we suggest using the following encryption regex to only encrypt data and stringData values.\nThis leaves non-sensitive fields, like the secret&rsquo;s name, unencrypted and human readable.<\/p>\n<\/blockquote>\n<p>All the configuration can be found in the <a href=\"https:\/\/getsops.io\/docs\/#encrypting-using-age\">SOPS docs<\/a>. Let&rsquo;s now look\ninto the specifics using our new setup with either Ansible or Kubernetes.<\/p>\n<h2 id=\"sops-for-ansible\" class=\"group\">\n SOPS for Ansible<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#sops-for-ansible\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Ansible can automatically process (decrypt) SOPS-encrypted files with the [Community SOPS Collection](Community SOPS Collection).<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-sh\" data-lang=\"sh\"><span style=\"display:flex;\"><span>ansible-galaxy collection install community.sops\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Additionally in my <code>ansible.cfg<\/code> I enabled this plugin\n(<a href=\"https:\/\/docs.ansible.com\/ansible\/latest\/reference_appendices\/config.html#variable-plugins-enabled\">see docs<\/a>) via<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span>vars_plugins_enabled=host_group_vars,community.sops.sops\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Now, taken from the official Ansible docs:<\/p>\n<blockquote>\n<p>After the plugin is enabled, correctly named group and host vars files will be transparently decrypted with SOPS.<\/p>\n<p>The files must end with one of these extensions:<\/p>\n<p>.sops.yaml<br>\n.sops.yml<br>\n.sops.json<\/p>\n<\/blockquote>\n<p>That&rsquo;s it. You can now encrypt your group or host vars files and Ansible can automatically decrypt them.<\/p>\n<h2 id=\"sops-for-kubernetes\" class=\"group\">\n SOPS for Kubernetes<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#sops-for-kubernetes\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>SOPS can be used with Kubernetes with the <a href=\"https:\/\/github.com\/viaduct-ai\/kustomize-sops\">KSOPS Kustomize Plugin<\/a>.\nThe configuration we already prepared, we only need to apply KSOPS to our cluster.<\/p>\n<p>I use the following manifest - see more examples in my <a href=\"https:\/\/codeberg.org\/codedge\/hlab\/src\/branch\/main\/k8s\/infra\/controllers\/ksops\/ksops.yaml\">homelab repository<\/a>:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">apiVersion<\/span>: viaduct.ai\/v1\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">kind<\/span>: ksops\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">metadata<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">name<\/span>: secret-generator\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">annotations<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">config.kubernetes.io\/function<\/span>: |<span style=\"color:#6c7086\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> exec:\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> path: ksops<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">files<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - service_a.sops.yaml\n<\/span><\/span><span style=\"display:flex;\"><span> - service_b.sops.yaml\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div>"},{"title":"My TrueNAS custom NAS build (2025)","link":"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/","pubDate":"Fri, 31 Oct 2025 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/","description":"<p>Since years I have an off-the-shelf NAS and always wanted to build my own. I wanted somthing I control myself, I am\nindependent from any vendor support, something I can modify to my needs. So I build a rather <em>old-skool NAS<\/em> 6 months ago.<\/p>\n<p>So, why is this <em>old-skool<\/em>? Well, considering the hardware it is old-skool, as it only uses old hardware, that was\nnew 5-8 years ago. Taking into account my requirements together with the hardware it is a perfect match (for me).\nAfter I built this NAS I value older (and thus cheaper) hardware a lot more, than I did before. I understood, that\na lot of newer hardware produced, is often totally overkill for me and my use-cases.<\/p>\n<h2 id=\"design-goals\" class=\"group\">\n Design goals<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#design-goals\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>I use my NAS for mainly the following tasks:<\/p>\n<ul>\n<li><strong>Backups<\/strong>\n<ul>\n<li>Storing photos and important documents in combination with paperless-ngx<\/li>\n<li>Storing backups from my K8s Longhorn volumes<\/li>\n<\/ul>\n<\/li>\n<li><strong>Media streaming:<\/strong> I stream music and music with jellyfin to all my devices<\/li>\n<\/ul>\n<p>Regarding the backups, there is no need for a hardware RAID aka RAID controller as I will use software for this. With that I am free\nto choose which RAID level I need whenever time for an upgrade has come.<\/p>\n<p>For the streaming part, I need to make sure the hardware is capable of doing transcoding properly.<\/p>\n<p>Further requirements:<\/p>\n<ul>\n<li><strong>Energy efficiency:<\/strong> My NAS runs quite some hours per day, so it needs to be somehow energy efficient to not spent\ntons of money. I had the goal of 25W tops.<\/li>\n<li><strong>Off-the-shelf hardware:<\/strong> I want some basic hardware, can be 3-5 years old, something that is always available in\nhardware shops around the corner, or on Ebay.<\/li>\n<li><strong>Minimal costs:<\/strong> I had the limit of 300 EUR tops without hard disks and I almost did all the searching for parts\non Ebay or the German Kleinanzeigen.<\/li>\n<li><strong>Silence:<\/strong> The NAS is placed in my office at home and not in some closet or basement. So I work all day next to it,\nthat is why it needs to be as silent as it can be.<\/li>\n<\/ul>\n<p>These requirements narrow down the decisions regarding the hardware a lot, but still leave plenty of parts to select\nfrom.<\/p>\n<h2 id=\"best-hardware\" class=\"group\">\n Best hardware?<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#best-hardware\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>To check which parts fit together I used <a href=\"https:\/\/pcpartpicker.com\">pcpartpicker.com<\/a>. You get a rough idea about\nthe costs and I cannot match incompatible parts. That is already a huge help of getting a first idea of the system.<\/p>\n<h3 id=\"components\" class=\"group\">\n Components<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#components\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><h4 id=\"case\" class=\"group\">\n Case<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#case\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p><strong>Fractal Node 304: 79 EUR, new<\/strong><\/p>\n<p>Why?<br>\nIt jsut looks super slick. I need to admit it is not as big as it probably should be. It can carry up to 6 disk, or\nas much as you mainboard can control. I am with 2 SSDs and 2 HDDs right now and the case inside is pretty packed.\nStill enough, that the air can properly flow, but&hellip;<\/p>\n<p>This is just a small complaint. In general I am very happy with the choice.<\/p>\n<h4 id=\"cpu\" class=\"group\">\n CPU<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#cpu\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p><strong>Intel i3-9100: 30 EUR, used<\/strong><\/p>\n<p>Why?<br>\nThis is a good mix of a cheap part and still enough power for my requirements - transcoding. The CPU has an integrated\ngraphics card, the Intel UHD Graphics 630b. This helps with two things:<\/p>\n<ul>\n<li>no extra graphics card needed, which resolves in a lower energy consumption<\/li>\n<li>enough power to do transcoding for one stream (maybe two as well)<\/li>\n<\/ul>\n<p>The i3 is mostly idle and when doing transcoding it runs at around 30% including the OS. I think, never tested, I could\ntranscode 2 streams in parallel, but will give some more insights on this when tested.<\/p>\n<p>With the CPU I use the Noctua NH-U12S. Very very silent and keeps the temperature low.<\/p>\n<h4 id=\"ram\" class=\"group\">\n RAM<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#ram\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p><strong>Corsair Vengeance, DDR4: 45 EUR, new<\/strong><\/p>\n<p>Why?<br>\nWhen building the NAS, I wasn&rsquo;t able to fully grasp how much RAM the system would need. So I went with the <em>better safe\nthan sorry<\/em> approach and put 32GB (2x16GB modules) inside. The speed of the RAM is neclectable, it just needs to be enough.<\/p>\n<p>I late found out, that probably 16GB would also be way enough, as the current built only consumes roughly 4GB.<\/p>\n<h4 id=\"mainboard\" class=\"group\">\n Mainboard<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#mainboard\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p><strong>ASUS Prime H310I-Plus R2.0, LGA 1151, 65 EUR, used<\/strong><\/p>\n<p>Why?<br>\nI wanted a cheap mainboard, hopefully without Wifi and all the cr*p, but got this. No special preference for this,\nit just works. One important thing was, that it support <code>WOL<\/code> (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Wake-on-LAN\">Wake-on-LAN<\/a>)\nso I can put the NAS asleep over night and put it back on without touching it.<\/p>\n<h4 id=\"psu\" class=\"group\">\n PSU<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#psu\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p><strong>Corsair RM650x: 60 EUR, used<\/strong><\/p>\n<p>Why?<br>\nThe PSU has active cooling (fan) but it start at around 200W+. So the fan of never starts, as my NAS only consumes 60W\nmaximum when under load. An even lower wattage, f. ex. 450W, would have also be sufficient, but it was hard to get\npreffered PSU without spending a ton of money on it.<\/p>\n<h4 id=\"disks\" class=\"group\">\n Disks<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#disks\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p>I run separate disks for system and storage.<\/p>\n<ul>\n<li>Storage: 2x WD Red Plus 3.5&quot;, 8TB<\/li>\n<li>OS (system): 1x KINGSTON SSD SNVP325S2, 64GB<\/li>\n<li>Apps (system): 1x Samsung SSD 850 EVO, 250GB<\/li>\n<\/ul>\n<p>The storage is in RAID 1 (Mirror) mode, as data safety is most crucial for my needs.<\/p>\n<h4 id=\"power-usage\" class=\"group\">\n Power Usage<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#power-usage\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p>With the above mentioned configuration I get these wattages:<\/p>\n<ul>\n<li>idle: 25W<\/li>\n<li>load: 65W<\/li>\n<\/ul>\n<p>I consider these pretty good and I am happy I can run my NAS for several hours per day without needing to spend a liver\nto pay electricity.<\/p>\n<h2 id=\"operating-system-truenas\" class=\"group\">\n Operating System: TrueNAS<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#operating-system-truenas\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>As Operating System I went for <a href=\"https:\/\/www.truenas.com\/truenas-community-edition\/\">TrueNAS<\/a>. I also had a quick look\nat <a href=\"https:\/\/unraid.net\">Unraid<\/a> but went with TrueNAS in the end. I cannot tell why exactly TrueNAS convinced me more.<\/p>\n<p>I needed a system, that manages the RAID and storage in general, plus gives me a freedom to set up shares (SMB\/NFS)\nand let me run some custom software via Docker, when needed. TrueNAS handles all of this very well, without much effort.\nUpdates went smooth all the time.<\/p>\n<p>Additional to the backup\/storage functionality I run:<\/p>\n<ul>\n<li><a href=\"https:\/\/jellyfin.org\">Jellyfin<\/a> media server<\/li>\n<li><a href=\"https:\/\/garagehq.deuxfleurs.fr\">Garage<\/a> S3 API server (similar to Minio, but still maintained<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1<\/a><\/sup>)<\/li>\n<li><a href=\"https:\/\/netbird.io\">Netbird VPN<\/a> to access my files from everywhere<\/li>\n<\/ul>\n<p>Setting up users and shares is easily done via the UI, and also starting additional apps and containers runs stable\nand without any effort.<\/p>\n<p>I run some <a href=\"https:\/\/en.wikipedia.org\/wiki\/Data_scrubbing\">data scrubbing<\/a> tasks to keep my storage error-free for hopefully\na long time. There is a lot more tasks you can set up in TrueNAS to keep Data Protection high. Cloud Syncs, periodic snapshots\nor replications tasks - a nice UI provides with an easy setup.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_data_protection_tasks.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_data_protection_tasks_hu_3ffbb0f173e4072.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_data_protection_tasks_hu_166eb226d8154d31.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_data_protection_tasks_hu_3147d491420b6a96.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_data_protection_tasks_hu_c875d2d6fe0aaec9.webp\">\n\n <img class=\"img\" alt=\"TrueNAS Data Protection tasks\" title=\"TrueNAS Data Protection tasks\"\n src=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_data_protection_tasks.png\" loading=\"lazy\" decoding=\"async\"\n height=\"2570\" width=\"3894\">\n <\/picture>\n <\/a>\n \n <figcaption>\n TrueNAS Data Protection tasks\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<h3 id=\"netbird-vpn\" class=\"group\">\n Netbird VPN<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#netbird-vpn\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Netbird VPN is no default app, that can be pulled via TrueNAS own <em>marketplace<\/em> or community. Instead you need to\ncreate a little Docker Compose script.<\/p>\n<p>Via <em>Discover &gt; Install via YAML<\/em> (hidden by the three dots symbol), you can enter this <code>compose.yml<\/code>:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">services<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">netbird<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">cap_add<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - NET_ADMIN\n<\/span><\/span><span style=\"display:flex;\"><span> - SYS_ADMIN\n<\/span><\/span><span style=\"display:flex;\"><span> - SYS_RESOURCE\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">container_name<\/span>: netbird\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">environment<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - NB_SETUP_KEY=[YOUR_NETBIRD_SETUP_KEY]\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">hostname<\/span>: truenas\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">image<\/span>: netbirdio\/netbird:latest\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">network_mode<\/span>: host\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">volumes<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - netbird-client:\/var\/lib\/netbird\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">volumes<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">netbird-client<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">name<\/span>: netbird-client\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>With this snippet, an Netbird client launches and your TrueNAS instance is available inside your Netbird VPN.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_netbird_app.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_netbird_app_hu_737dd338181e0335.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_netbird_app_hu_3ebf3dcb812e49f7.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_netbird_app_hu_2c56bdfdb474d45a.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_netbird_app_hu_c41d72e3e4b7b2bf.webp\">\n\n <img class=\"img\" alt=\"Netbird on TrueNAS\" title=\"Netbird on TrueNAS\"\n src=\"https:\/\/www.codedge.de\/posts\/truenas-custom-nas-build-2025\/truenas_netbird_app.png\" loading=\"lazy\" decoding=\"async\"\n height=\"1340\" width=\"2760\">\n <\/picture>\n <\/a>\n \n <figcaption>\n Netbird on TrueNAS\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<h3 id=\"zfs-storage--datasets\" class=\"group\">\n ZFS Storage &amp; Datasets<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#zfs-storage--datasets\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>I run unencrypted ZFS storage with multiple datasets. Datasets are like a structure of the data stored, f. ex.\nmusic, movies, documents and so on. For each dataset you can set up share, NFS or SMB, to be mounted across your network.<\/p>\n<p>All datasets have these properties:<\/p>\n<ul>\n<li>Compression: LZ4<\/li>\n<li>Disabled <code>atime<\/code>: do not set access time per file<\/li>\n<li>Case sensitivity on<\/li>\n<li>ZFS deduplication: this is something I need to think about. It is only RAM-intensive, but I have plenty of free RAM<\/li>\n<\/ul>\n<p>All datasets share the same amount of storage. I just monitor manually which dataset takes how much space and get a\nnotification from TrueNAS when the storage hits critical levels.<\/p>\n<h2 id=\"network\" class=\"group\">\n Network<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#network\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>My NAS gets a static DHCP lease assigned by my router. I found networking a lot easier, when devices with central\nfunctionality have static IPs.<\/p>\n<p>As I power off my NAS overnight and want to automatically power it on during the day, I need <code>WOL<\/code> properly working.\nAlthough the mainboard supports WOL functionality, the onboard NIC does not seem to have working WOL functionality.<\/p>\n<p>I ran <code>ethtool enp3s0<\/code> and was presented with a <code>Wake-On: d<\/code>, which means Wake-on-LAN is disabled<sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2<\/a><\/sup>.<\/p>\n<p>I configured WOL via <code>ethtool<\/code> but needed to make sure it survives a reboot. A small service, <code>\/etc\/systemd\/system\/wol.service<\/code>,\nnow takes care of that.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">9\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ini\" data-lang=\"ini\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">[Unit]<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\">Description<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">Enable Wake On Lan<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">[Service]<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\">Type<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">oneshot<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\">ExecStart<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#a6e3a1\">\/sbin\/ethtool --change enp3s0 wol g<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">[Install]<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\">WantedBy<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">basic.target<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>With that my NAS supports WOL and I can use one of my other servers to power on the nas. For that I use <a href=\"https:\/\/linux.die.net\/man\/8\/ether-wake\">etherwake<\/a>\nand run it was cronjob somewhere to power on my NAS at a specific time.<\/p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>Minio dropped the building and support of Docker images. Besides that, there is an ongoing severe security issue\n<a href=\"https:\/\/github.com\/minio\/minio\/issues\/21647\">https:\/\/github.com\/minio\/minio\/issues\/21647<\/a>&#160;<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:2\">\n<p>More information about Wake-on-LAN <a href=\"https:\/\/wiki.archlinux.org\/title\/Wake-on-LAN\">https:\/\/wiki.archlinux.org\/title\/Wake-on-LAN<\/a>&#160;<a href=\"#fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<\/ol>\n<\/div>"},{"title":"Codeberg builds on Woodpecker CI in K8s","link":"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/","pubDate":"Mon, 27 Oct 2025 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/","description":"<p>I moved all my repositories to Codeberg and I am very happy with it. While CI\/CD and heavy build jobs cannot be lifted\nby Codeberg for all repositories hosted, the way to go is to outsource jobs into your own CI engine and onto your\nown hardware.<\/p>\n<p>Codeberg is great! But it lacks substatial funding compared to the billions, that have been invested in Github.\nTherefore some services might not be as free as in Github - you must put some work into making it running smooth for yourself.\nWhile I run builds for this website on a Codeberg runner, I thought about moving these build off of Codeberg into\nmy own CI\/CD engine.<\/p>\n<p>While there is a lot of guides around setting up a runner for Codeberg, which might be sufficient for some people,\nthe <a href=\"https:\/\/docs.codeberg.org\/ci\/#caveats\">recommendation<\/a> from Codeberg itself is to host your own Woodpecker instance.<\/p>\n<blockquote>\n<p>Resource usage must be reasonable for the intended use-case.\nCI requires substantial computing resources (cloning the repo and pulling the image, installing required tools, building and throwing everything away).\nTherefore, please consider twice how to create a good balance between ensuring code quality for your project and resource usage.<\/p>\n<\/blockquote>\n<p>This gives you far more freedom in terms of configurability. You can run build for different platform, very work- or compute-intense builds - all on\nyour own hardware with Codeber (and Forgejo) as your forge in the background.<\/p>\n<h2 id=\"create-an-oauth2-application\" class=\"group\">\n Create an OAuth2 application<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-an-oauth2-application\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Woodpecker CI does handle any user accounts itself, instead it uses the connected forge to supply the user information.\nThis is done using OAuth2 authentication. To make this work, a new OAuth2 application needs to be created in Codeberg.<\/p>\n<p>Go to your user <em>Settings<\/em> &gt; <em>Applications<\/em> and create a new one. While the name can be anything, the <code>Redirect URIs<\/code>\nneed to include your woodpecker instance - <code>https:\/\/[YOUR_DOMAIN]\/authorize<\/code>. The ending <code>\/authorize<\/code> is needed.\nAfter hitting save, note down the <strong>Client ID<\/strong> and <strong>Client Secret<\/strong> for the new app. We need these values for the\nWoodpecker configuration.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/codeberg_oauth2_app.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/codeberg_oauth2_app_hu_ecb1861031d57653.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/codeberg_oauth2_app_hu_cd4a7cf54d77aeb2.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/codeberg_oauth2_app_hu_6b5318c5c5bc21ca.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/codeberg_oauth2_app_hu_82245a1d0be63775.webp\">\n\n <img class=\"img\" alt=\"OAuth2 app in Codeberg\" title=\"OAuth2 app in Codeberg\"\n src=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/codeberg_oauth2_app.png\" loading=\"lazy\" decoding=\"async\"\n height=\"1446\" width=\"1668\">\n <\/picture>\n <\/a>\n \n <figcaption>\n OAuth2 app in Codeberg\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<h2 id=\"installation-woodpecker-with-helm\" class=\"group\">\n Installation Woodpecker with Helm<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#installation-woodpecker-with-helm\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Before starting with the Woodpecker installation, we need to create a secret holding our OAuth2 credentials.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">apiVersion<\/span>: v1\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">kind<\/span>: Secret\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">metadata<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">name<\/span>: codeberg-woodpecker-credentials\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">namespace<\/span>: woodpecker\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">type<\/span>: Opaque\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">stringData<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">WOODPECKER_FORGEJO_CLIENT<\/span>: [OAuth 2 Client ID]\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">WOODPECKER_FORGEJO_SECRET<\/span>: [OAuth 2 Client Secret]\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">WOODPECKER_AGENT_SECRET<\/span>: [Random string]\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>The <a href=\"https:\/\/woodpecker-ci.org\/docs\/administration\/configuration\/agent#agent_secret\"><code>WOODPECKER_AGENT_SECRET<\/code><\/a> can easily be generated with <code>openssl rand -hex 32<\/code>.<\/p>\n<p>Woodpecker provides a handy <a href=\"https:\/\/github.com\/woodpecker-ci\/helm\">Helm chart<\/a> for the installation. Some important\nenvironment variables I want to highlight:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"background-color:#45475a\"><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><span style=\"background-color:#45475a\"><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">21\n<\/span><\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">22\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">23\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">24\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">25\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">26\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">27\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">28\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">29\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">server<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">enabled<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">env<\/span>:\n<\/span><\/span><span style=\"display:flex; background-color:#45475a\"><span> <span style=\"color:#cba6f7\">WOODPECKER_OPEN<\/span>: <span style=\"color:#a6e3a1\">&#34;true&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">WOODPECKER_HOST<\/span>: <span style=\"color:#a6e3a1\">&#34;https:\/\/[YOUR_DOMAIN]&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">WOODPECKER_FORGEJO<\/span>: <span style=\"color:#a6e3a1\">&#34;true&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">WOODPECKER_FORGEJO_URL<\/span>: <span style=\"color:#a6e3a1\">&#34;https:\/\/codeberg.org&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">extraSecretNamesForEnvFrom<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - codeberg-woodpecker-credentials\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">metrics<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">enabled<\/span>: <span style=\"color:#fab387\">false<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">persistentVolume<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">enabled<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">storageClass<\/span>: longhorn\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ingress<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">enabled<\/span>: <span style=\"color:#fab387\">false<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">agent<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">enabled<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">env<\/span>:\n<\/span><\/span><span style=\"display:flex; background-color:#45475a\"><span> <span style=\"color:#cba6f7\">WOODPECKER_MAX_WORKFLOWS<\/span>: <span style=\"color:#fab387\">2<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">WOODPECKER_BACKEND_K8S_NAMESPACE<\/span>: woodpecker\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">WOODPECKER_BACKEND_K8S_VOLUME_SIZE<\/span>: 10G\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">extraSecretNamesForEnvFrom<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - codeberg-woodpecker-credentials\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">replicaCount<\/span>: <span style=\"color:#fab387\">2<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">persistence<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">enabled<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">existingClaim<\/span>: woodpecker-agent\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><ul>\n<li><code>WOODPECKER_OPEN<\/code>: I left this to <code>true<\/code> as otherwise, when loggin in newly, I got the error <em>&ldquo;Registraton is closed&rdquo;<\/em>.<\/li>\n<li><code>WOODPECKER_MAX_WORKFLOWS<\/code>: I set this two 2 parallel workflows per agent.<\/li>\n<\/ul>\n<p>The important <code>*_FORGEJO<\/code> environment variables can be checked in the <a href=\"https:\/\/woodpecker-ci.org\/docs\/administration\/configuration\/forges\/forgejo\">official Woodpecker docs<\/a>.<\/p>\n<p>After installing your instance you can login with your Codeberg credentials, username and password. It will then automatically fetch\nyour repositories and let you select for which repositories you want to run builds.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/woodpecker_repos.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/woodpecker_repos_hu_fb893869e6814d4e.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/woodpecker_repos_hu_736e30a4bb84b9bb.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/woodpecker_repos_hu_17acb0e2dff16ed0.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/woodpecker_repos_hu_7a214c37bcd4a65c.webp\">\n\n <img class=\"img\" alt=\"Codeberg repos in Woodpecker\" title=\"Codeberg repos in Woodpecker\"\n src=\"https:\/\/www.codedge.de\/posts\/codeberg-build-woodpeckerci-k8s\/woodpecker_repos.png\" loading=\"lazy\" decoding=\"async\"\n height=\"412\" width=\"1517\">\n <\/picture>\n <\/a>\n \n <figcaption>\n Codeberg repos in Woodpecker\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<h2 id=\"sample-woodpecker-workflow\" class=\"group\">\n Sample Woodpecker workflow<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#sample-woodpecker-workflow\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Analogous to <code>.github<\/code> and <code>.forgejo<\/code>, configuration files for Woodpecker can either be in a<\/p>\n<ul>\n<li><code>.woodpecker<\/code> folder plus <code>&lt;workflow_name&gt;.yaml<\/code> or<\/li>\n<li><code>.woodpecker.yaml<\/code> file directly.<\/li>\n<\/ul>\n<p>I prefer the first one with having a separate folder. For my blog I created a mini test workflow in the file <code>.woodpecker\/tests.yaml<\/code>:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">when<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">event<\/span>: pull_request\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">steps<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\"># https:\/\/gohugo.io\/troubleshooting\/audit\/<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">audit<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">depends_on<\/span>: []\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">image<\/span>: <span style=\"color:#a6e3a1\">&#34;hugomods\/hugo:exts-0.151.2&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">commands<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - .\/bin\/hugo-audit.sh\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div>"},{"title":"Random wallpaper with swaybg","link":"https:\/\/www.codedge.de\/posts\/random-wallpaper-swaybg\/","pubDate":"Sat, 11 Oct 2025 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/random-wallpaper-swaybg\/","description":"<p>Setting a wallpaper in Sway, with swaybg, is easy. Unfortunately there is no way of setting a random wallpaper automatically\nout of the box. Here is a little helper script to do that.<\/p>\n<p>The script is based on a post from Silvain Durand<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1<\/a><\/sup> with some slight modifications. I just linked the script my\nsway config instead of setting a background there.<\/p>\n<p><strong>Sway config<\/strong>: <code>~\/.config\/sway\/config<\/code><\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-text\" data-lang=\"text\"><span style=\"display:flex;\"><span># ...\n<\/span><\/span><span style=\"display:flex;\"><span>exec ~\/scripts\/swaybg\/rand_wallpaper.sh\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>The script spawns a new <code>swaybg<\/code> instance, changes the wallpaper, and kills the old instance. With this approach there\nis no flickering of the background when changing.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">#!\/usr\/bin\/env sh\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"># Wallpaper directory<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f5e0dc\">WP_FOLDER<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span>~\/wallpaper\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"># Time in seconds to change wallpaper<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f5e0dc\">WAIT_TIME<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#fab387\">599<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">while<\/span> true; <span style=\"color:#cba6f7\">do<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">PID<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#cba6f7\">$(<\/span>pidof swaybg<span style=\"color:#cba6f7\">)<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">FILE<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#cba6f7\">$(<\/span>find <span style=\"color:#a6e3a1\">&#34;<\/span><span style=\"color:#f5e0dc\">$WP_FOLDER<\/span><span style=\"color:#a6e3a1\">&#34;<\/span> -type f -name <span style=\"color:#a6e3a1\">&#39;*.png&#39;<\/span> -o -name <span style=\"color:#a6e3a1\">&#39;*.jpg&#39;<\/span> | shuf -n1<span style=\"color:#cba6f7\">)<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> swaybg -i <span style=\"color:#a6e3a1\">&#34;<\/span><span style=\"color:#f5e0dc\">$FILE<\/span><span style=\"color:#a6e3a1\">&#34;<\/span> -m fill &amp;\n<\/span><\/span><span style=\"display:flex;\"><span> sleep <span style=\"color:#fab387\">1<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#89dceb\">kill<\/span> <span style=\"color:#a6e3a1\">&#34;<\/span><span style=\"color:#f5e0dc\">$PID<\/span><span style=\"color:#a6e3a1\">&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> sleep <span style=\"color:#a6e3a1\">&#34;<\/span><span style=\"color:#f5e0dc\">$WAIT_TIME<\/span><span style=\"color:#a6e3a1\">&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">done<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>An always up-to-date version can be found in my <a href=\"https:\/\/codeberg.org\/codedge\/dotfiles\/src\/branch\/main\/scripts\/swaybg\/executable_rand_wallpaper.sh\">dotfiles<\/a>.<\/p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>Original script from Silvain Durand: <a href=\"https:\/\/sylvaindurand.org\/dynamic-wallpapers-with-sway\/\">https:\/\/sylvaindurand.org\/dynamic-wallpapers-with-sway\/<\/a>&#160;<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<\/ol>\n<\/div>"},{"title":"Modern messaging: Running your own XMPP server","link":"https:\/\/www.codedge.de\/posts\/modern-messaging-running-your-own-xmpp-server\/","pubDate":"Tue, 16 Sep 2025 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/modern-messaging-running-your-own-xmpp-server\/","description":"<p>Since a years we know, or might suspect, our chats are listend on,\nour uploaded files are sold for advertising or what purpose ever and the chance our social messengers leak our private data is\nincredibly high. It is about time to work against this.<\/p>\n<p>Since 3 years the European Commission works on a plan to automatically monitor all chat,\nemail and messenger conversations.<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1<\/a><\/sup><sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2<\/a><\/sup> If this is going to pass, and I strongly hope it will not, the European Union\nis moving into a direction we know from states suppressing freedom of speech.<\/p>\n<p>I went for setting up my own XMPP server, as this does not have any big resource requirements\nand still support clustering (for high-availabilty purposes), encryption via OMEMO, file sharing\nand has support for platforms and operating systems. Also the ecosystem with clients and multiple use cases evolved\nover the years to provide rock-solid software and solutions for multi-user chats or event audio and video calls.<\/p>\n<style type=\"text\/css\">\n \n .notice {\n --title-color: #fff;\n --title-background-color: #6be;\n --content-color: #444;\n --content-background-color: #e7f2fa;\n }\n\n .notice.info {\n --title-background-color: #fb7;\n --content-background-color: #fec;\n }\n\n .notice.tip {\n --title-background-color: #5a5;\n --content-background-color: #efe;\n }\n\n .notice.warning {\n --title-background-color: #c33;\n --content-background-color: #fee;\n }\n\n \n @media (prefers-color-scheme:dark) {\n .notice {\n --title-color: #fff;\n --title-background-color: #069;\n --content-color: #ddd;\n --content-background-color: #023;\n }\n\n .notice.info {\n --title-background-color: #a50;\n --content-background-color: #420;\n }\n\n .notice.tip {\n --title-background-color: #363;\n --content-background-color: #121;\n }\n\n .notice.warning {\n --title-background-color: #800;\n --content-background-color: #400;\n }\n }\n\n body.dark .notice {\n --title-color: #fff;\n --title-background-color: #069;\n --content-color: #ddd;\n --content-background-color: #023;\n }\n\n body.dark .notice.info {\n --title-background-color: #a50;\n --content-background-color: #420;\n }\n\n body.dark .notice.tip {\n --title-background-color: #363;\n --content-background-color: #121;\n }\n\n body.dark .notice.warning {\n --title-background-color: #800;\n --content-background-color: #400;\n }\n\n \n .notice {\n padding: 18px;\n line-height: 24px;\n margin-bottom: 24px;\n border-radius: 4px;\n color: var(--content-color);\n background: var(--content-background-color);\n }\n\n .notice p:last-child {\n margin-bottom: 0\n }\n\n \n .notice-title {\n margin: -18px -18px 12px;\n padding: 4px 18px;\n border-radius: 4px 4px 0 0;\n font-weight: 700;\n color: var(--title-color);\n background: var(--title-background-color);\n }\n\n \n .icon-notice {\n display: inline-flex;\n align-self: center;\n margin-right: 8px;\n }\n\n .icon-notice img,\n .icon-notice svg {\n height: 1em;\n width: 1em;\n fill: currentColor;\n }\n\n .icon-notice img,\n .icon-notice.baseline svg {\n top: .125em;\n position: relative;\n }\n<\/style><div class=\"notice info\" >\n <p class=\"notice-title\">\n <span class=\"icon-notice baseline\">\n <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"92 59.5 300 300\">\n <path d=\"M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z\"\/>\n<\/svg>\n\n <\/span>Info<\/p><p>All steps and settings are bundled in a repository containing Ansible roles: <a href=\"https:\/\/codeberg.org\/codedge\/chat\">https:\/\/codeberg.org\/codedge\/chat<\/a><\/p><\/div>\n\n<p>All code snippets written below work in either Debian os Raspberry Pi OS.<\/p>\n<h2 id=\"setting-up-your-own-xmpp-server\" class=\"group\">\n Setting up your own XMPP server<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#setting-up-your-own-xmpp-server\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>The connection from your client to the XMPP server is encrypted and we need certificates for our server.\nFirst thing to do is setting up our domains and point it to the IP - both IPv4 and IPv6 is supported and we can specify\nboth later in our configuration.<\/p>\n<p>I assume the server is going to be run under <code>xmpp.example.com<\/code> and you all the following domains have been set up.<\/p>\n<div class=\"table-container\">\n <table>\n <thead>\n <tr>\n <th>Type<\/th>\n <th>Name<\/th>\n <th>Notes<\/th>\n <\/tr>\n <\/thead>\n <tbody>\n <tr>\n <td>A<\/td>\n <td>xmpp.example.com<\/td>\n <td>your main xmpp server address<\/td>\n <\/tr>\n <tr>\n <td>A<\/td>\n <td>conference.xmpp.example.com<\/td>\n <td>needed for MUC (Multi User Chat)<\/td>\n <\/tr>\n <tr>\n <td>A<\/td>\n <td>proxy.xmpp.example.com<\/td>\n <td>needed for SOCKS5 proxy support<\/td>\n <\/tr>\n <tr>\n <td>A<\/td>\n <td>pubsub.xmpp.example.com<\/td>\n <td>needed for publish\/subscribe support<\/td>\n <\/tr>\n <tr>\n <td>A<\/td>\n <td>upload.xmpp.example.com<\/td>\n <td>needed for file uploads<\/td>\n <\/tr>\n <tr>\n <td>A<\/td>\n <td>stun.xmpp.example.com<\/td>\n <td>needed for audio&amp;video calling<\/td>\n <\/tr>\n <tr>\n <td>A<\/td>\n <td>turn.xmpp.example.com<\/td>\n <td>needed for audio&amp;video calling<\/td>\n <\/tr>\n <\/tbody>\n<\/table>\n\n<\/div>\n\n<p>Fill in the IPv6 addresses accordingly.<\/p>\n<p><a href=\"https:\/\/www.ejabberd.im\/\">ejabberd<\/a> is a robust server software, that is included in most Linux distributions.<\/p>\n<p><strong>Install from Process One repository<\/strong><br>\nI discovered ProcessOne, the company behind <em>ejabberd<\/em>, also provides a <a href=\"https:\/\/repo.process-one.net\">Debian repository<\/a>.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>curl -o \/etc\/apt\/sources.list.d\/ejabberd.list https:\/\/repo.process-one.net\/ejabberd.list\n<\/span><\/span><span style=\"display:flex;\"><span>curl -o \/etc\/apt\/trusted.gpg.d\/ejabberd.gpg https:\/\/repo.process-one.net\/ejabberd.gpg\n<\/span><\/span><span style=\"display:flex;\"><span>apt update\n<\/span><\/span><span style=\"display:flex;\"><span>apt install ejabberd\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p><strong>Install from Github<\/strong><br>\nTo get the most recent one, I use the packages offered in their <a href=\"https:\/\/github.com\/processone\/ejabberd\">code repository<\/a>.\nInstalling version 25.07 just download the asset from the release:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>curl -L <span style=\"color:#89b4fa\">\\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\"><\/span> -o \/tmp\/ejabberd_2507.deb <span style=\"color:#89b4fa\">\\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\"><\/span> https:\/\/github.com\/processone\/ejabberd\/releases\/download\/25.07\/ejabberd_25.07-1_amd64.deb\n<\/span><\/span><span style=\"display:flex;\"><span>apt install \/tmp\/ejabberd_2507.deb\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Make sure the fowolling ports are opened in your firewall, taken from <a href=\"https:\/\/docs.ejabberd.im\/admin\/guide\/security\/\">ejabberd firewall settings<\/a>.<\/p>\n<ul>\n<li><strong>5222<\/strong>: Jabber\/XMPP client connections, plain or STARTTLS<\/li>\n<li><strong>5223<\/strong>: Jabber client connections, using the old SSL method<\/li>\n<li><strong>5269<\/strong>: Jabber\/XMPP incoming server connections<\/li>\n<li><strong>5280\/5443<\/strong>: HTTP\/HTTPS for Web Admin and many more<\/li>\n<li><strong>7777<\/strong>: SOCKS5 file transfer proxy<\/li>\n<li><strong>3478\/5349<\/strong>: STUN+TURN\/STUNS+TURNS service<\/li>\n<\/ul>\n<p>Port <code>1883<\/code>, used for MQTT, is also mentioned in the ejabberd docs, but we do not use this in our setup. So this port stays closed.<\/p>\n<p>Depending how you installed ejabberd the config file is either at <code>\/etc\/ejabberd\/conf\/ejabberd.yml<\/code>\nor <code>\/opt\/ejabberd\/conf\/ejabberd.yml<\/code>.<\/p>\n<h3 id=\"general-configuration\" class=\"group\">\n General configuration<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#general-configuration\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>The configuration is a balance of 70:30 between having a privacy-focused setup for your users and meeting most of the suggestions\nof the <a href=\"https:\/\/compliance.conversations.im\/?ref=process-one.net\">XMPP complicance test<\/a>. That means, settings that\nprotect the provacy of the users are higher rated despite not passing the test.<\/p>\n<p>Therefore notable privacy and security settings are:<\/p>\n<ul>\n<li>XMPP over HTTP is disabled (<a href=\"https:\/\/docs.ejabberd.im\/admin\/configuration\/modules\/#mod_bosh\">mod_bosh<\/a>)<\/li>\n<li>Discover then a user last accessed a server is disabled (<a href=\"https:\/\/docs.ejabberd.im\/admin\/configuration\/modules\/#mod_last\">mod_last<\/a>)<\/li>\n<li>Delete uploaded files on a regular base (see <a href=\"#enable-file-uploads\">upload config<\/a>)<\/li>\n<li>Register account via a web page is disabled (<a href=\"https:\/\/docs.ejabberd.im\/admin\/configuration\/modules\/#mod_register_web\">mod_register_web<\/a>)<\/li>\n<li>In-band registration can be enabled, default off, captcha secured (<a href=\"https:\/\/docs.ejabberd.im\/admin\/configuration\/modules\/#mod_register\">mod_register<\/a>, <a href=\"#registration\">see registration config<\/a>)<\/li>\n<\/ul>\n<div class=\"notice info\" >\n <p class=\"notice-title\">\n <span class=\"icon-notice baseline\">\n <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"92 59.5 300 300\">\n <path d=\"M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z\"\/>\n<\/svg>\n\n <\/span>Info<\/p><p>The configuration file is in YAML format. Keep an eye for indentation.<\/p><\/div>\n\n<p>Let&rsquo;s start digging into the configuration.<\/p>\n<p><strong>Set the domain of your server<\/strong><\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">hosts<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - xmpp.example.com\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p><strong>Set the database type<\/strong><br>\nInstead of using the default <code>mnesia<\/code> type, we opt for <code>sql<\/code>, better said <code>sqlite<\/code>.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">default_db<\/span>: sql\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">host_config<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">xmpp.example.com<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">auth_method<\/span>: sql\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">sql_type<\/span>: sqlite\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p><strong>Generate DH params<\/strong><br>\nGenerate a fresh set of params for the DH key exchange. In your terminal run<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>sudo mkdir -p \/opt\/ejabberd <span style=\"color:#89dceb;font-weight:bold\">&amp;&amp;<\/span> <span style=\"color:#89b4fa\">\\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\"><\/span> openssl dhparam -out \/opt\/ejabberd\/dhparams.pem <span style=\"color:#fab387\">4096<\/span> \n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>and link the new file in the ejabberd configuration.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">7\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">define_marco<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\"># Other config<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#39;DH_fiLE&#39;<\/span>: <span style=\"color:#a6e3a1\">&#34;\/opt\/ejabberd\/dhparams.pem&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"># ...<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">c2s_dhfile<\/span>: <span style=\"color:#a6e3a1\">&#39;DH_FILE&#39;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">s2s_dhfile<\/span>: <span style=\"color:#a6e3a1\">&#39;DH_FILE&#39;<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p><strong>Ensure TLS for server-to-server connections<\/strong><br>\nUse TLS for server-to-server (s2s) connections.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>s2s_use_starttls: required\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p><strong>The listners<\/strong><br>\nThe listeners aka <code>request_handlers<\/code> inside the config especially for <code>\/admin<\/code>, <code>\/captcha<\/code>, <code>\/upload<\/code> and <code>\/ws<\/code> are important.\nAll of them listen on port <code>5443<\/code>. Only one request handler is attached to port <code>5280<\/code>, the <code>\/.well-known\/acme-challenge<\/code>.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">21\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">22\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">23\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">24\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">25\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">26\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">27\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">28\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">29\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">30\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">31\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">32\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">33\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">34\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">35\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">36\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">37\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">38\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">39\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">40\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">41\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">42\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">43\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">44\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">45\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">46\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">47\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">48\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">49\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">50\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">51\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">52\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">53\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">54\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">55\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">56\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">57\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">58\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">59\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">60\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">61\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">62\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">63\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">64\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">listen<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> -\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">port<\/span>: <span style=\"color:#fab387\">5222<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip<\/span>: <span style=\"color:#a6e3a1\">&#34;::&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">module<\/span>: ejabberd_c2s\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">max_stanza_size<\/span>: <span style=\"color:#fab387\">262144<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">shaper<\/span>: c2s_shaper\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">access<\/span>: c2s\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">starttls_required<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">protocol_options<\/span>: <span style=\"color:#a6e3a1\">&#39;TLS_OPTIONS&#39;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> -\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">port<\/span>: <span style=\"color:#fab387\">5223<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip<\/span>: <span style=\"color:#a6e3a1\">&#34;::&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">module<\/span>: ejabberd_c2s\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">max_stanza_size<\/span>: <span style=\"color:#fab387\">262144<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">shaper<\/span>: c2s_shaper\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">access<\/span>: c2s\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">tls<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">protocol_options<\/span>: <span style=\"color:#a6e3a1\">&#39;TLS_OPTIONS&#39;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> -\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">port<\/span>: <span style=\"color:#fab387\">5269<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip<\/span>: <span style=\"color:#a6e3a1\">&#34;::&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">module<\/span>: ejabberd_s2s_in\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">max_stanza_size<\/span>: <span style=\"color:#fab387\">524288<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> -\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">port<\/span>: <span style=\"color:#fab387\">5270<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip<\/span>: <span style=\"color:#a6e3a1\">&#34;::&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">module<\/span>: ejabberd_s2s_in\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">tls<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">max_stanza_size<\/span>: <span style=\"color:#fab387\">524288<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> -\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">port<\/span>: <span style=\"color:#fab387\">5443<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip<\/span>: <span style=\"color:#a6e3a1\">&#34;::&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">module<\/span>: ejabberd_http\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">tls<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">protocol_options<\/span>: <span style=\"color:#a6e3a1\">&#39;TLS_OPTIONS&#39;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ciphers<\/span>: <span style=\"color:#a6e3a1\">&#39;TLS_CIPHERS&#39;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">request_handlers<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">\/admin<\/span>: ejabberd_web_admin\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">\/captcha<\/span>: ejabberd_captcha\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">\/upload<\/span>: mod_http_upload\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">\/ws<\/span>: ejabberd_http_ws\n<\/span><\/span><span style=\"display:flex;\"><span> -\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">port<\/span>: <span style=\"color:#fab387\">5280<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip<\/span>: <span style=\"color:#a6e3a1\">&#34;::&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">module<\/span>: ejabberd_http\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">tls<\/span>: <span style=\"color:#fab387\">false<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">request_handlers<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">\/.well-known\/acme-challenge<\/span>: ejabberd_acme\n<\/span><\/span><span style=\"display:flex;\"><span> -\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">port<\/span>: <span style=\"color:#fab387\">3478<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip<\/span>: <span style=\"color:#a6e3a1\">&#34;::&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">transport<\/span>: udp\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">module<\/span>: ejabberd_stun\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">use_turn<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">## The server&#39;s public IPv4 address:<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">turn_ipv4_address<\/span>: <span style=\"color:#a6e3a1\">&#34;{{ ipv4 }}&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">## The server&#39;s public IPv6 address:<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">turn_ipv6_address<\/span>: <span style=\"color:#a6e3a1\">&#34;{{ ipv6 }}&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> -\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">port<\/span>: <span style=\"color:#fab387\">1883<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip<\/span>: <span style=\"color:#a6e3a1\">&#34;::1&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">module<\/span>: mod_mqtt\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">backlog<\/span>: <span style=\"color:#fab387\">1000<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><h3 id=\"acls--access-rules\" class=\"group\">\n ACLs &amp; Access rules<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#acls--access-rules\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>For adminstration of ejabberd we need a user with admin rights and properly set up ACLs and access rules.\nThere is a separat section for ACLs inside the config in which we set up an admin user name <code>root<\/code>. The name of the user\nis important for later, when we actually create this user.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>acl:\n<\/span><\/span><span style=\"display:flex;\"><span> admin:\n<\/span><\/span><span style=\"display:flex;\"><span> user: root\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\"># ... <\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> proxy_users:\n<\/span><\/span><span style=\"display:flex;\"><span> server: xmpp.example.com\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>The <code>access_rules<\/code> should already be set up, just to confirm that you have a correct entry for the <code>configure<\/code> action.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>access_rules:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">#...<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> configure:\n<\/span><\/span><span style=\"display:flex;\"><span> allow: admin\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Now the new <code>root<\/code> user needs to be create by running this command on the console.\nWatch out to put in the correct domain.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>ejabberdctl register root xmpp.example.com <span style=\"color:#a6e3a1\">&#39;my_password_here&#39;<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Another user can be registered with the same command.<br>\nWe set <code>root<\/code> as the admin user in the config previously. That is how ejabberd knows which user has admin permissions.<\/p>\n<h3 id=\"enable-file-uploads\" class=\"group\">\n Enable file uploads<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#enable-file-uploads\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Enabling file uploads is done with <a href=\"https:\/\/docs.ejabberd.im\/admin\/configuration\/modules\/#mod_http_upload\"><code>mod_http_upload<\/code><\/a>.\nFirst, create a folder where the uploads should be stored.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>mkdir -p \/var\/www\/ejabberd\/upload\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Now update the ejabberd configuration like this:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">modules<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">#...<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">mod_http_upload<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">put_url<\/span>: https:\/\/@HOST@:5443\/upload\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">docroot<\/span>: \/var\/www\/ejabberd\/upload\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">max_size<\/span>: <span style=\"color:#fab387\">10000000<\/span> <span style=\"color:#6c7086;font-style:italic\">## 10 MB<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">thumbnail<\/span>: <span style=\"color:#fab387\">false<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">custom_headers<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;Access-Control-Allow-Origin&#34;: <\/span><span style=\"color:#a6e3a1\">&#34;https:\/\/@HOST@&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;Access-Control-Allow-Methods&#34;: <\/span><span style=\"color:#a6e3a1\">&#34;GET,HEAD,PUT,OPTIONS&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;Access-Control-Allow-Headers&#34;: <\/span><span style=\"color:#a6e3a1\">&#34;Content-Type&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">#...<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>The allowed file upload size is defined in the <code>max_size<\/code> param and is set to 10MB.<\/p>\n<p>Make sure, to delete uploaded files in a reasonable amount of time via cronjob. This is an example of a cronjob,\nthat deletes files that are older than 1 week.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span><span style=\"color:#fab387\">0<\/span> * * * * find \/var\/www\/ejabberd\/upload\/upload -type f -cmin +10080 -exec rm -rf <span style=\"color:#89dceb;font-weight:bold\">{}<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><h3 id=\"registration\" class=\"group\">\n Registration<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#registration\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Registration in ejabberd is done via <a href=\"https:\/\/docs.ejabberd.im\/admin\/configuration\/modules\/#mod_register\"><code>mod_register<\/code><\/a>\nand can be enabled with these entries in the config file:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">access_rules<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">#...<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">register<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">allow<\/span>: all\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">#...<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">modules<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">#...<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">mod_register<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">access<\/span>: register\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">ip_access<\/span>: all\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">captcha_protected<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">password_strength<\/span>: <span style=\"color:#fab387\">64<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">#...<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>If you want to enable registration for your server make sure you enable a captcha for it.\nOtherwise you will get a lot of spam and fake registrations.<\/p>\n<p>ejabberd provides a working <a href=\"https:\/\/github.com\/processone\/ejabberd\/blob\/master\/tools\/captcha.sh\">captcha script<\/a>,\nthat you can copy to your server and link in your configuration. You will need <code>imaggemagick<\/code> and <code>gstools<\/code> installed\non you system. In the <code>ejabberd.yml<\/code> config file<\/p>\n<h2 id=\"add-tls\" class=\"group\">\n Add TLS<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#add-tls\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>ejabberd can provision TLS certificates on its own. No need to install <em>certbot<\/em>. To not expose ejabberd directly to\nthe internet, <code>nginx<\/code> is put in front of the XMPP server. Instead of using <em>nginx<\/em>, every other web server (caddy, &hellip;)\nor proxy can be used as well.<\/p>\n<p>Here is a sample config for <em>nginx<\/em>:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">21\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">22\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">23\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">24\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">25\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">26\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">27\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">28\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">29\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">30\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span>server {\n<\/span><\/span><span style=\"display:flex;\"><span> listen 80;\n<\/span><\/span><span style=\"display:flex;\"><span> listen [::]:80;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> server_name xmpp.example.com proxy.xmpp.example.com pubsub.xmpp.example.com conference.xmpp.example.com;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> access_log \/var\/log\/nginx\/20-xmpp.access.log;\n<\/span><\/span><span style=\"display:flex;\"><span> error_log \/var\/log\/nginx\/20-xmpp.error.log;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> location = \/.well-known\/host-meta {\n<\/span><\/span><span style=\"display:flex;\"><span> default_type &#39;application\/xrd+xml&#39;;\n<\/span><\/span><span style=\"display:flex;\"><span> add_header Access-Control-Allow-Origin &#39;*&#39; always;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> root \/var\/www\/ejabberd;\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span> location = \/.well-known\/host-meta.json {\n<\/span><\/span><span style=\"display:flex;\"><span> default_type &#39;application\/jrd+json&#39;;\n<\/span><\/span><span style=\"display:flex;\"><span> add_header Access-Control-Allow-Origin &#39;*&#39; always;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> root \/var\/www\/ejabberd;\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> location \/ {\n<\/span><\/span><span style=\"display:flex;\"><span> proxy_set_header X-Real-IP $remote_addr;\n<\/span><\/span><span style=\"display:flex;\"><span> proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n<\/span><\/span><span style=\"display:flex;\"><span> proxy_set_header X-Forwarded-Proto $scheme;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> proxy_pass http:\/\/127.0.0.1:5280;\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span>}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><h3 id=\"alternative-connection-methods\" class=\"group\">\n Alternative connection methods<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#alternative-connection-methods\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>The nginx vhosts offers files, <code>host-meta<\/code> and <code>host-meta.json<\/code>, for indicating which other connection methods (BOSH, WS) your server offers. The details can be read in <a href=\"https:\/\/xmpp.org\/extensions\/xep-0156.html\">XEP-0156<\/a> extension.\nOpposite to the examples in the XEP, there is no BOSH, but only a websocket connection our server offers. The BOSH part is removed from the config file.<\/p>\n<p><strong>host-meta<\/strong><\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-xml\" data-lang=\"xml\"><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">&lt;?xml version=&#39;1.0&#39; encoding=&#39;utf-8&#39;?&gt;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">&lt;XRD<\/span> <span style=\"color:#89b4fa\">xmlns=<\/span><span style=\"color:#a6e3a1\">&#39;http:\/\/docs.oasis-open.org\/ns\/xri\/xrd-1.0&#39;<\/span><span style=\"color:#cba6f7\">&gt;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&lt;Link<\/span> <span style=\"color:#89b4fa\">rel=<\/span><span style=\"color:#a6e3a1\">&#34;urn:xmpp:alt-connections:websocket&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#89b4fa\">href=<\/span><span style=\"color:#a6e3a1\">&#34;wss:\/\/xmpp.example.com:5443\/ws&#34;<\/span> <span style=\"color:#cba6f7\">\/&gt;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">&lt;\/XRD&gt;<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p><strong>host-meta.json<\/strong><\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">8\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-json\" data-lang=\"json\"><span style=\"display:flex;\"><span>{\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;links&#34;<\/span>: [\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;rel&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;urn:xmpp:alt-connections:websocket&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;href&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;wss:\/\/xmpp.example.com:5443\/ws&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span> ]\n<\/span><\/span><span style=\"display:flex;\"><span>}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Put that file in a folder your nginx serves. Have a look at the path and URL it is expected to be, see <code>.well-known<\/code>.<\/p>\n<h2 id=\"choose-your-client\" class=\"group\">\n Choose your client<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#choose-your-client\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Clients I can recommend are <a href=\"https:\/\/profanity-im.github.io\">Profanity<\/a>, an easy to use command-line client,\nand <a href=\"https:\/\/monal-im.org\">Monal<\/a> for MacOS and iOS. A good overview of client can be found on the offical <a href=\"https:\/\/xmpp.org\/software\/\">XMPP website<\/a>.<\/p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>Citizen-led initiative collecting information about Chat Controle\n<a href=\"https:\/\/fightchatcontrol.eu\">https:\/\/fightchatcontrol.eu<\/a>&#160;<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:2\">\n<p>Explanation by Patrick Breyer, former member of the European Parliament\n<a href=\"https:\/\/www.patrick-breyer.de\/en\/posts\/chat-control\/\">https:\/\/www.patrick-breyer.de\/en\/posts\/chat-control\/<\/a>&#160;<a href=\"#fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<\/ol>\n<\/div>"},{"title":"Proton Authenticator: Don't you want diversification?","link":"https:\/\/www.codedge.de\/posts\/proton-authenticator-diversification\/","pubDate":"Tue, 12 Aug 2025 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/proton-authenticator-diversification\/","description":"<p>When Proton released its new authenticator in July 2025 I had mixed feelings about that. A new authenticator app although there is a bunch of viable, secure and privacy-respecting as well as well-maintained <a href=\"https:\/\/www.privacyguides.org\/en\/multi-factor-authentication\/#aegis-authenticator-android\">alternatives<\/a>.<br>\nWhen did you as an ordinary user trusted all your secrets, mail, vpn, password, 2FA and so on, to one company and what happened next?<\/p>\n<p>Just upfront, I do not have anything personally or technically against Proton Authenticator. It is way newer and maintained than the old Twilio Authy some people using. But the there is something more to this.<br>\nThere is an English saying: <em>Don&rsquo;t put all your eggs in one basket<\/em>. That applies to investments aka <em>diversification<\/em>, as well as it applies relying solely on one company with your communication and secrets.<\/p>\n<p>Last time most of trusted one company with all their digital life belongings that was probably Google. We had mail there, our calenders, we shared private stuff on Google+ - <a href=\"https:\/\/en.wikipedia.org\/wiki\/Google%2B\">Google+<\/a>, can you remember? - then there is Google Authenticator and Search, Youtube, etc. Effectively your digital life was on Google. You created a vendor lock-in yourself.<\/p>\n<blockquote>\n<p>And now we do the vendor lock-in again and think it will be different?<\/p>\n<\/blockquote>\n<p>There might be people saying that this time things are different. Proton is based in Europe. Argh, no they are not. Proton is based in Switzerland and that is not inside the European Union. Furthermore everything is encrypted and Proton knows shit what is inside your communication, people say. Well, in 2019 it turned out Proton lied about logging<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1<\/a><\/sup> and IMHO their CEO has issues with being political neutral<sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2<\/a><\/sup>. Your trust your digital guts to Proton - I mean using a VPN for privacy reasons, or using encrypted email, or a password manager and then they hand over logs, metadata and more. Not the nicest move. Let us just keep it with that.<\/p>\n<p>Wouldn&rsquo;t you be better of to diversify your tools to different provider, different countries and jurisdictions and maybe also self-hosting some of them?<\/p>\n<p>You do not want to create SPOF for your communication and secure data, so do not do that by just using one provider for everything.<\/p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>French climate activist was arrested to Swiss authorities based on Proton logs<br>\n<a href=\"https:\/\/arstechnica.com\/information-technology\/2021\/09\/privacy-focused-protonmail-provided-a-users-ip-address-to-authorities\/\">https:\/\/arstechnica.com\/information-technology\/2021\/09\/privacy-focused-protonmail-provided-a-users-ip-address-to-authorities\/<\/a>&#160;<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:2\">\n<p>Proton Mail Faces Backlash Over Claims of Political Neutrality Amid CEO\u2019s Praise for Republican Party<br>\n<a href=\"https:\/\/techstory.in\/proton-mail-faces-backlash-over-claims-of-political-neutrality-amid-ceos-praise-for-republican-party\/\">https:\/\/techstory.in\/proton-mail-faces-backlash-over-claims-of-political-neutrality-amid-ceos-praise-for-republican-party\/<\/a>&#160;<a href=\"#fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<\/ol>\n<\/div>"},{"title":"Hugo with Codeberg and BunnyCDN","link":"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/","pubDate":"Thu, 05 Jun 2025 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/","description":"<p>While enshittification is rolling across a lot of US-based services, let&rsquo;s try to host our static Hugo page with EU-based services only.\nDomain, deployment, CDN: we&rsquo;re going back to the roots - or into a more modern tech-era for European internet services.<\/p>\n<p><em>Disclaimer<\/em>:<br>\n<em>I never had technical problems with Github or Cloudflare. The switch to move my page to providers based inside the European Union is based on a bunch of actions US-based internet companies did or the US government in general<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1<\/a><\/sup>. I do not want to support them anymore.\nThe services I recommend here are tested and trusted by me. Links to these services use the affiliate programm of that particular services to support me using their services.<\/em><\/p>\n<p>I moved my website from using Github + Cloudflare pages to a new setup using Codeberg and Bunny.net CDN. During the last months I already read about tools and providers others are using or recommending. I have settled for the following <em>new<\/em> providers:<\/p>\n<ul>\n<li><strong><a href=\"https:\/\/www.do.de\/?ap=iBLuNyg5ixd4\">Domain Offensive<\/a><\/strong>: Domain provider<\/li>\n<li><strong><a href=\"https:\/\/codeberg.org\/\">Codeberg<\/a><\/strong>: Code hosting\/development platform and deployment tool<\/li>\n<li><strong><a href=\"https:\/\/bunny.net?ref=33dyrw5dkq\">Bunny.net<\/a><\/strong>: CDN for hosting the website<\/li>\n<\/ul>\n<p>I just need a workflow that consists of these three steps<\/p>\n<ul>\n<li>write content,<\/li>\n<li>deploy (run through various steps, I can control and modify) and<\/li>\n<li>enjoy your website.<\/li>\n<\/ul>\n<p>I can see, that the following setup is going to last for a long time, as it provides a basic, but extensible architecture with the common things you would expect and probably already enjoy, like<\/p>\n<ul>\n<li>insane speed<\/li>\n<li>automatic TLS certificate<\/li>\n<li>a deploy process you as the owner control mostly yourself.<\/li>\n<\/ul>\n<p>\n\n\n\n\n\n\n<figure>\n <img class=\"img\"\n alt=\"codeberg_bunnycdn.svg\"\n title=\"codeberg_bunnycdn.svg\"\n src=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/codeberg_bunnycdn.svg\" loading=\"lazy\" decoding=\"async\"\n height=\"\" width=\"\">\n \n <figcaption>codeberg_bunnycdn.svg<\/figcaption>\n \n<\/figure>\n\n\n<\/p>\n<h2 id=\"domain-offensive\" class=\"group\">\n Domain Offensive<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#domain-offensive\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>I am with them for several years and never had any issues. The domains work, their UI is rather functional than too much clickly-clicky. I like!\nYou just register with them, payment can be done via Credit Card, PayPal, SEPA and more. They even give you a little credit in case something goes wrong with the payment method selected. In any case, you will keep your domains all time and not getting kicked immediately. Their support was all the time very friendly and supportive.<\/p>\n<h2 id=\"codeberg\" class=\"group\">\n Codeberg<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#codeberg\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Codeberg is a non-profit source code hoster from Germany. Compared to Github and others, there is no intention to monetize your code or knowledge<sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2<\/a><\/sup>. The service runs on Forgejo, a rewritten fork of Gitea<sup id=\"fnref:3\"><a href=\"#fn:3\" class=\"footnote-ref\" role=\"doc-noteref\">3<\/a><\/sup>.<\/p>\n<p>They are still small compared to the big players Github and Gitlab, but already provide all functionality for most of us, to run your software project on it. You have got code hosting, Codeberg actions - which is almost the same as Github actions, an issue tracker, packages, release, user management and further more.<\/p>\n<div class=\"notice tip\" >\n <p class=\"notice-title\">\n <span class=\"icon-notice baseline\">\n <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"300.5 134 300 300\">\n <path d=\"M551.281 252.36c0-3.32-1.172-6.641-3.515-8.985l-17.774-17.578c-2.344-2.344-5.469-3.711-8.789-3.711-3.32 0-6.445 1.367-8.789 3.71l-79.687 79.493-44.141-44.14c-2.344-2.344-5.469-3.712-8.79-3.712-3.32 0-6.444 1.368-8.788 3.711l-17.774 17.579c-2.343 2.343-3.515 5.664-3.515 8.984 0 3.32 1.172 6.445 3.515 8.789l70.704 70.703c2.343 2.344 5.664 3.711 8.789 3.711 3.32 0 6.64-1.367 8.984-3.71l106.055-106.056c2.343-2.343 3.515-5.468 3.515-8.789ZM600.5 284c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z\"\/>\n<\/svg>\n\n <\/span>Tip<\/p><p>If you cannot find actions in your repository make sure you enable it first. In the repository settings, visit &ldquo;Units&rdquo; and click the &ldquo;Enable Actions&rdquo; checkbox.<\/p><\/div>\n\n<p>To deploy your code create a <code>.forgejo\/workflows\/deploy.yaml<\/code> file in the root folder of your project. This is a 1-to-1 copy of how you structure workflows in Github. Inside your <code>deploy.yaml<\/code> you can use the following code.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">21\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">22\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">23\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">24\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">25\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">26\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">27\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">28\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">29\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">30\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">31\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">32\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">33\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">34\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">35\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">36\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">37\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">38\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">39\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">40\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">41\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">42\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">43\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">44\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">45\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">46\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">47\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">48\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">49\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">50\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">51\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">52\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">53\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">54\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">55\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">56\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">57\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">58\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">59\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">60\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">61\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">62\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">63\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">64\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">65\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">66\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">name<\/span>: Deploy to BunnyCDN\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">on<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">push<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">branches<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - main\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">workflow_dispatch<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">env<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">HUGO_BASE_URL<\/span>: https:\/\/www.my-site.com\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">HUGO_VERSION<\/span>: <span style=\"color:#fab387\">0.147.3<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">jobs<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">build<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">runs-on<\/span>: codeberg-tiny\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">container<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">image<\/span>: <span style=\"color:#a6e3a1\">&#34;hugomods\/hugo:exts-${{ env.HUGO_VERSION }}&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">steps<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">name<\/span>: Clone the repository\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">uses<\/span>: https:\/\/code.forgejo.org\/actions\/checkout@v4\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">with<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">submodules<\/span>: recursive\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">fetch-depth<\/span>: <span style=\"color:#fab387\">0<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">name<\/span>: Generate static files with Hugo\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">env<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">HUGO_ENV<\/span>: production\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">run<\/span>: |<span style=\"color:#6c7086\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> hugo \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> --gc \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> --minify \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> --baseURL &#34;${{ env.HUGO_BASE_URL }}&#34; \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> --destination cdn<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">name<\/span>: Upload generated files\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">uses<\/span>: https:\/\/code.forgejo.org\/actions\/upload-artifact@v3\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">with<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">name<\/span>: cdn-content\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">path<\/span>: cdn\/\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">deploy<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">needs<\/span>: [ build ]\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">runs-on<\/span>: codeberg-tiny\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">container<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">image<\/span>: node:23-bookworm\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">steps<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">name<\/span>: Restore artifact\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">uses<\/span>: https:\/\/code.forgejo.org\/actions\/download-artifact@v3\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">with<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">name<\/span>: cdn-content\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">path<\/span>: cdn\/\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">name<\/span>: Install duck.sh\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">run<\/span>: |<span style=\"color:#6c7086\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> echo &#34;deb https:\/\/s3.amazonaws.com\/repo.deb.cyberduck.io stable main&#34; | tee \/etc\/apt\/sources.list.d\/cyberduck.list &gt; \/dev\/null\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> apt-key adv --keyserver keyserver.ubuntu.com --recv-keys FE7097963FEFBE72\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> apt-get update\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> apt-get install duck<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">name<\/span>: Deploy to Bunny.net\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">run<\/span>: |<span style=\"color:#6c7086\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> duck -y \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> --username codedge-de-prod \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> --password ${{ secrets.BUNNYNET_DEPLOY_KEY }} \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> --existing overwrite \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> --upload ftps:\/\/storage.bunnycdn.com\/ .\/cdn\/<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">name<\/span>: Purge pull zone\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">run<\/span>: |<span style=\"color:#6c7086\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> curl -X POST \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> -H &#34;AccessKey: ${{ secrets.BUNNYNET_API_KEY }}&#34; \\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> https:\/\/api.bunny.net\/pullzone\/&lt;PULL_ZONE_ID&gt;\/purgeCache<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>I created a 2-step deploy process: first build the files, second deploy to Bunny. When using the workflow from above, have a look at the following variables and adjust to your needs.<\/p>\n<ul>\n<li><code>HUGO_BASE_URL<\/code> base uri: set this to the URL your page should be run at<\/li>\n<li><code>&lt;PULL_ZONE_ID&gt;<\/code>: set this to the id of your pull zone at Bunny, we&rsquo;ll come to that in a moment<\/li>\n<li><code>BUNNYNET_DEPLOY_KEY<\/code>: this is deploy key, with which you deploy your files to Bunny<\/li>\n<li><code>BUNNYNET_API_KEY<\/code>: this is the general API key, which is used for purging the pull zone<\/li>\n<\/ul>\n<p><strong>Important:<\/strong><br>\n<code>BUNNYNET_DEPLOY_KEY<\/code> and <code>BUNNYNET_API_KEY<\/code> are two separate keys.\nThe deploy key you can get in your push zone in the <em>FTP &amp; API Access<\/em> tab. The general API key you need to copy from your <em>Account Settings<\/em> page.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_ftp.webp\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_ftp_hu_adf50d487daf436d.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_ftp_hu_843ebfe7f3526fe8.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_ftp_hu_471a0993e6d6c943.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_ftp_hu_a829fd1ef39d3d5f.webp\">\n\n <img class=\"img\" alt=\"FTP Upload\" title=\"FTP Upload\"\n src=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_ftp.webp\" loading=\"lazy\" decoding=\"async\"\n height=\"1768\" width=\"2220\">\n <\/picture>\n <\/a>\n \n <figcaption>\n FTP Upload\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<p>As you can see, we use <em>duck.sh<\/em><sup id=\"fnref:4\"><a href=\"#fn:4\" class=\"footnote-ref\" role=\"doc-noteref\">4<\/a><\/sup> for the deployment, which is done via FTP.<\/p>\n<div class=\"notice info\" >\n <p class=\"notice-title\">\n <span class=\"icon-notice baseline\">\n <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"92 59.5 300 300\">\n <path d=\"M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z\"\/>\n<\/svg>\n\n <\/span>Info<\/p><p><strong>duck.sh<\/strong>:\nI was not able to find some more lightweight CLI FTP tool to transfer multiple files. Probably some shell wizard would write a script utilizing curl. I&rsquo;d be happy if you can ping or mention me in the Fediverse if you found some alternative.<\/p><\/div>\n\n<p>At the end of the deploy step the pull zones are purged. This invalidates the cache of Bunny so it is going to fetch the new version, that we just pushed.<\/p>\n<h2 id=\"deploy-to-bunnynet\" class=\"group\">\n Deploy to Bunny.net<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#deploy-to-bunnynet\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>The most &ldquo;work&rdquo; needs to be done on Bunnys side. What we need is a<\/p>\n<ul>\n<li><strong>push zone<\/strong>: the primary region where your content will be uploaded to. Select some place close to where you live. Also select at least one geo replication location to have your content replicated<\/li>\n<li><strong>pull zone<\/strong>: this can be one or more zone, where your content is pushed to. I set this up with Frankfurt (EU), Singapore (Asia) and New York (North America). Additionally you can select place in Africa and South America.<\/li>\n<\/ul>\n<h3 id=\"create-a-push-zone\" class=\"group\">\n Create a push zone<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-a-push-zone\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Let&rsquo;s create our push zone by clicking on <em>Storage<\/em> on the left side and <em>Add Storage Zone<\/em>.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_push_zone.webp\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_push_zone_hu_356b91a72c513539.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_push_zone_hu_3b8ec854d093234a.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_push_zone_hu_62fefca35e323f9.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_push_zone_hu_d2c6f2babe8bb44a.webp\">\n\n <img class=\"img\" alt=\"Bunny.net Push Zone\" title=\"Bunny.net Push Zone\"\n src=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_push_zone.webp\" loading=\"lazy\" decoding=\"async\"\n height=\"764\" width=\"1978\">\n <\/picture>\n <\/a>\n \n <figcaption>\n Bunny.net Push Zone\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<p>The name for the push zone can be freely chosen. The storage tier can be probably left to <em>Standard<\/em>. Only if you require response times between 1-5ms choose <em>Edge (SSD)<\/em>, which comes with almost 3-times the pricing of the <em>Standard<\/em> tier. Now set up the geo replication, check the pricing per GB and your done with the first part.<\/p>\n<p>I am currently at $0.025\/GB with GEO Replication in Frankfurt, Singapore and New York.<\/p>\n<h3 id=\"create-a-pull-zone\" class=\"group\">\n Create a pull zone<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-a-pull-zone\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>You can attach the pull zone by clicking <em>Connect Pull Zone<\/em>.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_pull_zone.webp\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_pull_zone_hu_74a79610e48e360a.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_pull_zone_hu_16a2d1434931047d.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_pull_zone_hu_54e9254348f69451.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_pull_zone_hu_f64d86b9a4c1f4f5.webp\">\n\n <img class=\"img\" alt=\"Bunny.net Pull Zone\" title=\"Bunny.net Pull Zone\"\n src=\"https:\/\/www.codedge.de\/posts\/hugo-with-codeberg-bunnycdn\/bunny_pull_zone.webp\" loading=\"lazy\" decoding=\"async\"\n height=\"734\" width=\"2230\">\n <\/picture>\n <\/a>\n \n <figcaption>\n Bunny.net Pull Zone\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<p>Enter the name of the pull zone. Again, set the storage tier to _Standard. Adjust the <em>Pricing Zones<\/em> to what ever suits your needs and wallet.<\/p>\n<p>When creating the pull zone enter the domain for your site as custom hostname. I entered <code>www.codedge.de<\/code>, as this should be the domain for my page. You can also go with an ANAME record, like <code>codedge.de<\/code> as an example for my site, but this is not recommended<sup id=\"fnref:5\"><a href=\"#fn:5\" class=\"footnote-ref\" role=\"doc-noteref\">5<\/a><\/sup>. Set <em>Force SSL<\/em> to true and you are good to go.<\/p>\n<h2 id=\"costs\" class=\"group\">\n Costs<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#costs\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Cost comparison is always a bit tricky. If we compare the costs between self-hosting your site on a VPS, most people would argue, that the VPS can also be utilized for some other things than just hosting a website. That is why I will just put the costs side-by-side - and in my opinion the price for this setup is very very competitive.<\/p>\n<p>I used Hetzner for Hosting and just a general pricing of 1 EUR\/month for the domain. Bunny charges a minimum of 1 EUR\/month in general up to a certain level of traffic. Have a look at their websites calculator to get a better indication.<\/p>\n<table class=\"data-table\">\n <thead>\n <tr>\n <th><\/th>\n <th style=\"text-align: right\">VPS<\/th>\n <th style=\"text-align: right\">Our workflow<\/th>\n <\/tr>\n <\/thead>\n <tbody>\n <tr>\n <td>Domain<\/td>\n <td style=\"text-align: right\">1 EUR<\/td>\n <td style=\"text-align: right\">1 EUR<\/td>\n <\/tr>\n <tr>\n <td>Hosting<\/td>\n <td style=\"text-align: right\">3.82 EUR<\/td>\n <td style=\"text-align: right\">1 EUR<\/td>\n <\/tr>\n <\/tbody>\n<\/table>\n<p>For me the amount of traffice served this is within the 1 EUR\/month.<\/p>\n<p>I hope you now enjoy your website being served from the EU :-)<\/p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p>Enshittification of the internet: <a href=\"https:\/\/locusmag.com\/2024\/05\/cory-doctorow-no-one-is-the-enshittifier-of-their-own-story\/\">and it continues<\/a>, <a href=\"https:\/\/arstechnica.com\/gadgets\/2025\/02\/as-internet-enshittification-marches-on-here-are-some-of-the-worst-offenders\/\">recent examples<\/a>&#160;<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:2\">\n<p>What is Codeberg e.V., see <a href=\"https:\/\/docs.codeberg.org\/getting-started\/what-is-codeberg\/#what-is-codeberg-e.v.%3F\">their mission<\/a>&#160;<a href=\"#fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:3\">\n<p><a href=\"https:\/\/forgejo.org\/2024-02-forking-forward\/\">Offical Forgejo statement<\/a> about soft and hard fork: <a href=\"https:\/\/forgejo.org\/2024-02-forking-forward\/\">https:\/\/forgejo.org\/2024-02-forking-forward\/<\/a>&#160;<a href=\"#fnref:3\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:4\">\n<p><a href=\"https:\/\/duck.sh\/\">https:\/\/duck.sh\/<\/a>&#160;<a href=\"#fnref:4\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:5\">\n<p><a href=\"https:\/\/bunny.net\/blog\/how-aname-dns-records-affect-cdn-routing\/\">How ANAME DNS records affect routing<\/a>&#160;<a href=\"#fnref:5\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<\/ol>\n<\/div>"},{"title":"Moving away from Authy","link":"https:\/\/www.codedge.de\/posts\/moving-away-from-authy\/","pubDate":"Mon, 19 May 2025 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/moving-away-from-authy\/","description":"<p>One year ago Authys desktop app was shut down. Their mobile app still works, but is literally a dead train. It was about time to move to some alternative - and there are not plenty.<\/p>\n<p>2FA\/Mutifactor developed to be a common thing over the last couple of years. Event non-technical persons understand the importance. While hardware keys, such as Yubikey (U2F, Universal Second Factor), are the better option<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1<\/a><\/sup>, using apps to generate TOTP<sup id=\"fnref:2\"><a href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\">2<\/a><\/sup> are widely used. With this is mind, I thought there must be plenty of alternatives in the market.<\/p>\n<p>While looking around in Reddit, on Google and checking out Privacy Guides<sup id=\"fnref:3\"><a href=\"#fn:3\" class=\"footnote-ref\" role=\"doc-noteref\">3<\/a><\/sup> to collect recommendations, I stumbled upon the ever same repeating lack of features for the 2FA apps out there. Features I expect from the new 2FA app:<\/p>\n<ul>\n<li>Desktop (macOS, Linux) and mobile app (iOS)<\/li>\n<li>Sync with multiple devices, end-to-end encrypted<\/li>\n<li>Must not require an internet connection<\/li>\n<li>Open source <del>would be a big plus<\/del> is mandatory<\/li>\n<\/ul>\n<p>The last point is funny, as Authy was closed source. I decided to not go this way again, and switch to a more transparent solution. If at this point you might think to develop your own TOTP solution would be possible - it is. There is nicely written article by Hendrik Erz<sup id=\"fnref:4\"><a href=\"#fn:4\" class=\"footnote-ref\" role=\"doc-noteref\">4<\/a><\/sup> on how to generate the codes.<\/p>\n<p>While in the research process, I discovered that Authy codes cannot be easily transferred to a different application, meaning there is no export functionality. WTF?! Although I do not have hundreds of accounts, the migration is going to take some time, for all accounts to be manually changed. But it is what it is - there is no way around.<\/p>\n<p>I had a quick look at these apps<\/p>\n<ul>\n<li><a href=\"https:\/\/2fas.com\/\">2FAS<\/a><\/li>\n<li><a href=\"https:\/\/ente.io\/auth\/\">ente auth<\/a><\/li>\n<li><a href=\"https:\/\/proton.me\/authenticator\">Proton Authenticator<\/a><\/li>\n<\/ul>\n<p>and in the end went for <strong>ente auth<\/strong>.<\/p>\n<h3 id=\"2fas\" class=\"group\">\n 2FAS<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#2fas\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>2FAS does also have all the features I wanted to see in a Multifactor app, but&hellip; they are not listed on Privacy Guides and they launched some crypto token<sup id=\"fnref:5\"><a href=\"#fn:5\" class=\"footnote-ref\" role=\"doc-noteref\">5<\/a><\/sup>. Both points weren&rsquo;t trustful, especially not the second one, to go for this app.<\/p>\n<h3 id=\"ente-auth\" class=\"group\">\n ente auth<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#ente-auth\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>ente auth has all the features I expect from a 2FA solution and has a long time record in providing stable, free and open source solutions.<\/p>\n<p>I am now with enthe auth for almost half a year and all I can say is, it just works. Syncing has never been a problem, the apps on the individual platforms work flawless, getting updates from time to time. All good! Pretty satisfied.<\/p>\n<h3 id=\"proton-authenticator\" class=\"group\">\n Proton Authenticator<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#proton-authenticator\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>The new Proton Authenticator made big waves and surely adds missing features to the whole security suite of Proton.\nThe main thing, that bugs me with Proton is, that the more data, apps, you name it, you move into their spere, the bigger the more you depend on them as a company. I have written my concerns about this in a <a href=\"posts\/proton-authenticator-diversification\/\">separate post<\/a>.<\/p>\n<div class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\">\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/Universal_2nd_Factor\">Universal Second Factor (U2F)<\/a> - Advantages and disadvantages&#160;<a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:2\">\n<p><a href=\"https:\/\/en.wikipedia.org\/wiki\/Time-based_one-time_password\">Time-based one-time password (TOTP)<\/a>&#160;<a href=\"#fnref:2\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:3\">\n<p><a href=\"https:\/\/www.privacyguides.org\/en\/multi-factor-authentication\/\">Privacy Guides<\/a>&#160;<a href=\"#fnref:3\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:4\">\n<p><a href=\"https:\/\/www.hendrik-erz.de\/post\/understanding-totp-two-factor-authentication-eli5\">Implement TOTP yourself<\/a>&#160;<a href=\"#fnref:4\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<li id=\"fn:5\">\n<p>Why does a two-factor authenticator app need an NFT? - <a href=\"https:\/\/nft.2fas.com\/\">https:\/\/nft.2fas.com\/<\/a>&#160;<a href=\"#fnref:5\" class=\"footnote-backref\" role=\"doc-backlink\">&#x21a9;&#xfe0e;<\/a><\/p>\n<\/li>\n<\/ol>\n<\/div>"},{"title":"Nuxt 3: Good to know","link":"https:\/\/www.codedge.de\/posts\/nuxt-3-good-to-know\/","pubDate":"Sun, 30 Apr 2023 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/nuxt-3-good-to-know\/","description":"<p>When switching my personal website to Nuxt 3 I found myself looking and researching all the small things you need to tackle, besides the content, to make it fly. Sitemap, robots.txt, proper page titles, include 3rd party JS and so on. While there is a lot written about a general setup of Nuxt, most blogs almost never talk about these small things.<\/p>\n<p>You can find all of these tips in the repository <a href=\"https:\/\/github.com\/codedge\/codedge.de\">codedge\/codedge.de<\/a> of this site.<\/p>\n<h2 id=\"dynamic-page-title\" class=\"group\">\n Dynamic page title<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#dynamic-page-title\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Most websites you a similar page title like this:<br>\n<code>&lt;name of the current page&gt;&lt;divider&gt;&lt;general name of the website&gt;<\/code><\/p>\n<p>For example: <code>Home | codedge<\/code><\/p>\n<p>Let&rsquo;s create that.<\/p>\n<p><strong>Prerequisites<\/strong><\/p>\n<ul>\n<li>You use the <a href=\"https:\/\/nuxt.com\/docs\/guide\/directory-structure\/pages\">pages<\/a> feature<\/li>\n<li>You have a central <code>app.vue<\/code><\/li>\n<\/ul>\n<p>In your <code>app.vue<\/code> you configure the central title content using the <code>useHead<\/code> configuration:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">8\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ts\" data-lang=\"ts\"><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">script<\/span> <span style=\"color:#89b4fa\">setup<\/span> <span style=\"color:#89b4fa\">lang<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;ts&#34;<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span>useHead({\n<\/span><\/span><span style=\"display:flex;\"><span> htmlAttrs<span style=\"color:#89dceb;font-weight:bold\">:<\/span> {\n<\/span><\/span><span style=\"display:flex;\"><span> lang<span style=\"color:#89dceb;font-weight:bold\">:<\/span> <span style=\"color:#a6e3a1\">&#34;en&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> },\n<\/span><\/span><span style=\"display:flex;\"><span> titleTemplate<span style=\"color:#89dceb;font-weight:bold\">:<\/span> <span style=\"color:#a6e3a1\">&#34;%s | codedge&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span>})\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;\/<span style=\"color:#cba6f7\">script<\/span>&gt;\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>This adds the general part, in my case <code>| codedge<\/code>, to every title for each page. To add the specific title for a page, open one of your <code>*.vue<\/code> files in the <code>pages\/<\/code> directory and add<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ts\" data-lang=\"ts\"><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">script<\/span> <span style=\"color:#89b4fa\">setup<\/span> <span style=\"color:#89b4fa\">lang<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;ts&#34;<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span>useHead({\n<\/span><\/span><span style=\"display:flex;\"><span> title<span style=\"color:#89dceb;font-weight:bold\">:<\/span> <span style=\"color:#a6e3a1\">&#34;My fancy title&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span>});\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;\/<span style=\"color:#cba6f7\">script<\/span>&gt;\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>to it. This is going to build a title like <code>My fancy title | codedge<\/code>.<\/p>\n<h2 id=\"conditionally-load-scripts\" class=\"group\">\n Conditionally load scripts<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#conditionally-load-scripts\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>You might want to load scripts, f. ex. tracking scripts, depending on your environment. I wanted to include the <a href=\"https:\/\/plausible.io\/\">Plausible<\/a> tracking script only on the production environment.<\/p>\n<p>In you <code>app.vue<\/code> you can do this:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ts\" data-lang=\"ts\"><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">script<\/span> <span style=\"color:#89b4fa\">setup<\/span> <span style=\"color:#89b4fa\">lang<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;ts&#34;<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">import<\/span> { Script } <span style=\"color:#cba6f7\">from<\/span> <span style=\"color:#a6e3a1\">&#34;@unhead\/schema&#34;<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">type<\/span> HeaderScript <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#89dceb\">Array<\/span>&lt;<span style=\"color:#cba6f7\">Script<\/span>&gt;;\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">let<\/span> scripts: <span style=\"color:#f38ba8\">HeaderScript<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> [];\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">let<\/span> plausible: <span style=\"color:#f38ba8\">Script<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> {\n<\/span><\/span><span style=\"display:flex;\"><span> src<span style=\"color:#89dceb;font-weight:bold\">:<\/span> <span style=\"color:#a6e3a1\">&#34;https:\/\/plausible.io\/js\/script.js&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> defer: <span style=\"color:#f38ba8\">true<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#34;data-domain&#34;<\/span><span style=\"color:#89dceb;font-weight:bold\">:<\/span> <span style=\"color:#a6e3a1\">&#34;&lt;SET YOUR DOMAIN HERE&gt;&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span>};\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">let<\/span> env<span style=\"color:#89dceb;font-weight:bold\">=<\/span>process.env.NODE_ENV.toUpperCase()\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">\/\/ Only include the tracking script in the production environment\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span><span style=\"color:#cba6f7\">if<\/span> (env <span style=\"color:#89dceb;font-weight:bold\">===<\/span> <span style=\"color:#a6e3a1\">&#34;PRODUCTION&#34;<\/span>) {\n<\/span><\/span><span style=\"display:flex;\"><span> scripts.push(plausible);\n<\/span><\/span><span style=\"display:flex;\"><span>}\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span>useHead({\n<\/span><\/span><span style=\"display:flex;\"><span> script: <span style=\"color:#f38ba8\">scripts<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span>})\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Make sure, you set your <code>data-domain<\/code> correctly according to your site.<\/p>\n<h2 id=\"using-images-with-markdown\" class=\"group\">\n Using images with Markdown<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#using-images-with-markdown\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>While including images in your repository might be a fast and straightforward solution you might want to consider using some image service like <a href=\"https:\/\/cloudinary.com\/\">Cloudinary<\/a> or <a href=\"https:\/\/imgix.com\/\">Imgix<\/a> as a central place for your images.<\/p>\n<p>I chose Cloudinary which is supported by the <a href=\"https:\/\/image.nuxtjs.org\/\">NuxtImage<\/a> plugin. After setting things up you might want to use this in your Markdown files. Using <code>&lt;nuxt-img&gt;<\/code> or <code>&lt;nuxt-picture&gt;<\/code> does unfortunately not work there. Wrapping this inside a <a href=\"https:\/\/content.nuxtjs.org\/guide\/writing\/mdc\/\">Markdown Component (MDC)<\/a> helps.<\/p>\n<p>Create a file in <code>components\/content\/Image.vue<\/code> and Add<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ts\" data-lang=\"ts\"><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">script<\/span> <span style=\"color:#89b4fa\">setup<\/span> <span style=\"color:#89b4fa\">lang<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;ts&#34;<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span>defineProps([<span style=\"color:#a6e3a1\">&#34;imgSrc&#34;<\/span>]);\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;\/<span style=\"color:#cba6f7\">script<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">template<\/span>&gt;&lt;<span style=\"color:#cba6f7\">nuxt<\/span><span style=\"color:#f38ba8\">-<\/span><span style=\"color:#89b4fa\">picture<\/span> <span style=\"color:#89b4fa\">provider<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;cloudinary&#34;<\/span> <span style=\"color:#f38ba8\">:<\/span><span style=\"color:#89b4fa\">src<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;imgSrc&#34;<\/span> \/&gt;&lt;\/<span style=\"color:#cba6f7\">template<\/span>&gt;\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>With that in place you can use NuxtImage in your Markdown files by writing<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ts\" data-lang=\"ts\"><span style=\"display:flex;\"><span><span style=\"color:#89dceb;font-weight:bold\">::<\/span>image{imgSrc<span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;&lt;path\/to\/your\/image.png&gt;&#34;<\/span>}\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89dceb;font-weight:bold\">::<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><h2 id=\"a-404-component\" class=\"group\">\n A 404 component<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#a-404-component\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>You want to serve a custom 404 page, properly styled, whenever a visitor hits an unknown sub-page of your site. Instead of customizing the <code>error.vue<\/code> file, as suggested by other people, I went for writing a custom component which only deals with the 404 error.<\/p>\n<p>Add a file <code>components\/NotFound.vue<\/code> and add the following:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-ts\" data-lang=\"ts\"><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">script<\/span> <span style=\"color:#89b4fa\">setup<\/span> <span style=\"color:#89b4fa\">lang<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;ts&#34;<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">const<\/span> event <span style=\"color:#89dceb;font-weight:bold\">=<\/span> useRequestEvent();\n<\/span><\/span><span style=\"display:flex;\"><span>setResponseStatus(event, <span style=\"color:#fab387\">404<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span>useHead({\n<\/span><\/span><span style=\"display:flex;\"><span> title<span style=\"color:#89dceb;font-weight:bold\">:<\/span> <span style=\"color:#a6e3a1\">&#34;404&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span>});\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;\/<span style=\"color:#cba6f7\">script<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">template<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span> &lt;<span style=\"color:#cba6f7\">div<\/span> <span style=\"color:#89b4fa\">class<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;flex flex-col items-center justify-center px-5 mt-24&#34;<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span> Ooops, page not found.\n<\/span><\/span><span style=\"display:flex;\"><span> &lt;\/<span style=\"color:#cba6f7\">div<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;\/<span style=\"color:#cba6f7\">template<\/span>&gt;\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Additionally to the content itself, we modify the page title and returning a proper 404 http status code.<\/p>\n<p>I use this component in my <code>pages\/[...slug.vue]<\/code> file (<a href=\"https:\/\/github.com\/codedge\/codedge.de\/blob\/main\/pages\/%5B...slug%5D.vue\">see here<\/a>) file. So whenever a request is matched by this catchall route, deliver the 404 page, as probably no other content was found.<\/p>\n<h2 id=\"sitemap\" class=\"group\">\n Sitemap<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#sitemap\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Currently neither Nuxt 3 or the Content module provides a way to generate a sitemap in the default installation. Luckily this is quick win to set up yourself.<\/p>\n<p>First, install the NPM package sitemap by running <code>yarn add -D sitemap<\/code>.\nSecond, create a file <code>server\/routes\/sitemap.xml.ts<\/code> with the content<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-js\" data-lang=\"js\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">import<\/span> { serverQueryContent } from <span style=\"color:#a6e3a1\">&#34;#content\/server&#34;<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">import<\/span> { SitemapStream, streamToPromise } from <span style=\"color:#a6e3a1\">&#34;sitemap&#34;<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">export<\/span> <span style=\"color:#cba6f7\">default<\/span> defineEventHandler(<span style=\"color:#cba6f7\">async<\/span> (event) =&gt; {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">const<\/span> docs <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#cba6f7\">await<\/span> serverQueryContent(event).find();\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">const<\/span> sitemap <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#cba6f7\">new<\/span> SitemapStream({\n<\/span><\/span><span style=\"display:flex;\"><span> hostname<span style=\"color:#89dceb;font-weight:bold\">:<\/span> <span style=\"color:#a6e3a1\">&#34;&lt;YOUR FQDN, f. ex. https:\/\/www.codedge.de&gt;&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> });\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">for<\/span> (<span style=\"color:#cba6f7\">const<\/span> doc <span style=\"color:#cba6f7\">of<\/span> docs) {\n<\/span><\/span><span style=\"display:flex;\"><span> sitemap.write({\n<\/span><\/span><span style=\"display:flex;\"><span> url<span style=\"color:#89dceb;font-weight:bold\">:<\/span> doc._path,\n<\/span><\/span><span style=\"display:flex;\"><span> changefreq<span style=\"color:#89dceb;font-weight:bold\">:<\/span> <span style=\"color:#a6e3a1\">&#34;monthly&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> });\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span> sitemap.end();\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">return<\/span> streamToPromise(sitemap);\n<\/span><\/span><span style=\"display:flex;\"><span>});\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Don&rsquo;t forget to update the hostname in the code. You can now open your sitemap at <code>https:\/\/your-domain\/sitemap.xml<\/code>.<\/p>"},{"title":"Auto-generate a social media image in Statamic","link":"https:\/\/www.codedge.de\/posts\/auto-generate-images\/","pubDate":"Mon, 07 Dec 2020 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/auto-generate-images\/","description":"<p>When writing blog posts you likely want to share your post on Twitter or any other social network to give your posts an audience. Let&rsquo;s stay with Twitter for this post. While writing blog posts might be the main task, the title and content is clear, there is often an image used to and pulled by social media.<\/p>\n<p>Instead of searching the web for meaningful backgrounds, why not just putting the title of your post and the name of your blog on an uni-color background. For creating this image we need to hook into Statamics <a href=\"https:\/\/statamic.dev\/extending\/events#content\">event system<\/a> to generate the image after saving the blog post.<\/p>\n<h2 id=\"creating-the-event-listener\" class=\"group\">\n Creating the event listener<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#creating-the-event-listener\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>To create a new listener that actually runs the image generation we run<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>php artisan make:listener GenerateBlogPostImage\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>which creates a new listener at <code>app\/Listeners\/GenerateBlogPostImage.php<\/code>. Next the listener needs to be run, when an entry is saved. For this the <a href=\"https:\/\/statamic.dev\/extending\/events#entrysaved\">EventSaved<\/a> event comes in handy. So let&rsquo;s register our listener by putting in it <code>app\/Providers\/EventServiceProvider.php<\/code>.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-php\" data-lang=\"php\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">use<\/span> App\\Listeners\\GenerateBlogPostImage;\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">use<\/span> Statamic\\Events\\EntrySaved;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">class<\/span> <span style=\"color:#f9e2af\">EventServiceProvider<\/span> <span style=\"color:#cba6f7\">extends<\/span> ServiceProvider\n<\/span><\/span><span style=\"display:flex;\"><span>{\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086\">\/**\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> * The event listener mappings for the application.\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> *\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> * @var array\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086\"> *\/<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">protected<\/span> <span style=\"color:#f5e0dc\">$listen<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> [\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ Some other events\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> EntrySaved<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">class<\/span> <span style=\"color:#89dceb;font-weight:bold\">=&gt;<\/span> [\n<\/span><\/span><span style=\"display:flex;\"><span> GenerateBlogPostImage<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">class<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> ],\n<\/span><\/span><span style=\"display:flex;\"><span> ];\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ Further stuff\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span>}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Now, whenever an entry is saved, our <code>GenerateBlogPostImage<\/code> listener is called and can do its magic, generating a <code>1200x627<\/code> pixel image for Twitter. For creating the images I am going to use the <a href=\"http:\/\/image.intervention.io\/\">Intervention Image library<\/a>. Back to our listener file we need to load the Intervention Image library and generate the image.<\/p>\n<p>Here is complete file:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">21\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">22\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">23\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">24\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">25\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">26\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">27\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">28\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">29\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">30\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">31\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">32\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">33\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">34\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">35\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">36\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">37\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">38\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">39\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">40\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">41\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">42\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">43\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">44\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">45\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">46\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">47\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">48\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">49\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">50\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">51\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">52\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">53\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">54\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-php\" data-lang=\"php\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">use<\/span> Illuminate\\Support\\Str;\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">use<\/span> Intervention\\Image\\ImageManager;\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">use<\/span> Statamic\\Events\\EntrySaved;\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">use<\/span> Statamic\\Entries\\Entry;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">class<\/span> <span style=\"color:#f9e2af\">GenerateBlogPostImage<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>{\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">const<\/span> <span style=\"color:#f9e2af\">IMAGE_WIDTH<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#fab387\">1200<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">const<\/span> <span style=\"color:#f9e2af\">IMAGE_HEIGHT<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#fab387\">627<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">protected<\/span> string <span style=\"color:#f5e0dc\">$baseImagePath<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">protected<\/span> <span style=\"color:#f5e0dc\">$imageManager<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">public<\/span> <span style=\"color:#cba6f7\">function<\/span> <span style=\"color:#89b4fa\">__construct<\/span>()\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$this<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">baseImagePath<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> public_path(<span style=\"color:#a6e3a1\">&#39;assets\/posts&#39;<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$this<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">imageManager<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#cba6f7\">new<\/span> ImageManager([\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#39;driver&#39;<\/span> <span style=\"color:#89dceb;font-weight:bold\">=&gt;<\/span> <span style=\"color:#a6e3a1\">&#39;imagick&#39;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> ]);\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">public<\/span> <span style=\"color:#cba6f7\">function<\/span> <span style=\"color:#89b4fa\">handle<\/span>(EntrySaved <span style=\"color:#f5e0dc\">$event<\/span>)<span style=\"color:#89dceb;font-weight:bold\">:<\/span> void\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086\">\/** @var Entry $entry *\/<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$entry<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#f5e0dc\">$event<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">entry<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">if<\/span>(<span style=\"color:#f5e0dc\">$entry<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">collectionHandle<\/span>() <span style=\"color:#89dceb;font-weight:bold\">==<\/span> <span style=\"color:#a6e3a1\">&#39;posts&#39;<\/span> <span style=\"color:#89dceb;font-weight:bold\">&amp;&amp;<\/span> <span style=\"color:#f5e0dc\">$entry<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">published<\/span>() <span style=\"color:#89dceb;font-weight:bold\">===<\/span> <span style=\"color:#cba6f7\">true<\/span>) {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ Create the image\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> <span style=\"color:#f5e0dc\">$image<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#f5e0dc\">$this<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">imageManager<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">canvas<\/span>(self<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">IMAGE_WIDTH<\/span>, self<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">IMAGE_HEIGHT<\/span>, <span style=\"color:#a6e3a1\">&#39;#E5E7EB&#39;<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ Title\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> <span style=\"color:#f5e0dc\">$image<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">text<\/span>(<span style=\"color:#f5e0dc\">$entry<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">title<\/span>, self<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">IMAGE_WIDTH<\/span><span style=\"color:#89dceb;font-weight:bold\">\/<\/span><span style=\"color:#fab387\">2<\/span>, self<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">IMAGE_HEIGHT<\/span><span style=\"color:#89dceb;font-weight:bold\">\/<\/span><span style=\"color:#fab387\">2<\/span>, <span style=\"color:#cba6f7\">function<\/span> (<span style=\"color:#f5e0dc\">$font<\/span>) {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">size<\/span>(<span style=\"color:#fab387\">46<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">color<\/span>(<span style=\"color:#a6e3a1\">&#39;#111827&#39;<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">file<\/span>(storage_path(<span style=\"color:#a6e3a1\">&#39;Rubik.ttf&#39;<\/span>));\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">align<\/span>(<span style=\"color:#a6e3a1\">&#39;center&#39;<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">valign<\/span>(<span style=\"color:#a6e3a1\">&#39;middle&#39;<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span> });\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ Website\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> <span style=\"color:#f5e0dc\">$image<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">text<\/span>(<span style=\"color:#a6e3a1\">&#39;codedge.de&#39;<\/span>, self<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">IMAGE_WIDTH<\/span><span style=\"color:#89dceb;font-weight:bold\">\/<\/span><span style=\"color:#fab387\">2<\/span>, self<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">IMAGE_HEIGHT<\/span><span style=\"color:#89dceb;font-weight:bold\">\/<\/span><span style=\"color:#fab387\">1.25<\/span>, <span style=\"color:#cba6f7\">function<\/span>(<span style=\"color:#f5e0dc\">$font<\/span>) {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">size<\/span>(<span style=\"color:#fab387\">28<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">color<\/span>(<span style=\"color:#a6e3a1\">&#39;#ff6633&#39;<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">file<\/span>(storage_path(<span style=\"color:#a6e3a1\">&#39;Rubik.ttf&#39;<\/span>));\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$font<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">align<\/span>(<span style=\"color:#a6e3a1\">&#39;center&#39;<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span> });\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ Save the image\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> <span style=\"color:#f5e0dc\">$imagesPath<\/span> <span style=\"color:#89dceb;font-weight:bold\">=<\/span> <span style=\"color:#f5e0dc\">$this<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">baseImagePath<\/span> <span style=\"color:#89dceb;font-weight:bold\">.<\/span> <span style=\"color:#a6e3a1\">&#39;\/&#39;<\/span> <span style=\"color:#89dceb;font-weight:bold\">.<\/span> Str<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">slug<\/span>(<span style=\"color:#f5e0dc\">$entry<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">title<\/span>)<span style=\"color:#89dceb;font-weight:bold\">.<\/span><span style=\"color:#a6e3a1\">&#39;.png&#39;<\/span>;\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#f5e0dc\">$image<\/span><span style=\"color:#89dceb;font-weight:bold\">-&gt;<\/span><span style=\"color:#89b4fa\">save<\/span>(<span style=\"color:#f5e0dc\">$imagesPath<\/span>, <span style=\"color:#fab387\">100<\/span>);\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span>}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>The requirement for Intervention is PHPs <code>imagick<\/code> extension. Alternatively you can use gd too. I restricted the generation to:<\/p>\n<ul>\n<li>posts in the <code>posts<\/code> collections<\/li>\n<li>only published items<\/li>\n<\/ul>\n<h2 id=\"intervention-image-library\" class=\"group\">\n Intervention Image library<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#intervention-image-library\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>I suspect there is more than one libray to use for this use-case, but I am pretty happy with Intervention. I never used it before, but it seems to be stable and does exactly what it should do.<\/p>\n<p><strong>Imagick or GD?<\/strong><\/p>\n<p>First I started with using <code>gd<\/code> but it turned out I cannot properly render text. When using gd you can specify built-in fonts 1 to 5, which wasn&rsquo;t my intention. I wanted to use my own font. With <code>imagick<\/code> I was able to render text properly with a custom <code>.ttf<\/code> file.<\/p>"},{"title":"Run Statamic on Vercel","link":"https:\/\/www.codedge.de\/posts\/run-statamic-on-vercel\/","pubDate":"Sat, 05 Dec 2020 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/run-statamic-on-vercel\/","description":"<p>When Tailwindcss 2 was <a href=\"https:\/\/blog.tailwindcss.com\/tailwindcss-v2\">released<\/a> I decided to give my blog a new update and also write about some more topics I had in mind. The blog ran on Hugo hosted on Netlify. While I never had issues with Netlify for some reasons Hugo wasn&rsquo;t just not made for me.<\/p>\n<p>It took me a fair amount of time to get Tailwindcss 2 up and running there, then I found some issues with generating my content,&hellip; all in all not a refreshing experience when you just want to write some new blog posts.<\/p>\n<p>In the meantime I feel in love with <a href=\"https:\/\/statamic.com\/\">Statamic<\/a> as I used it in another freelance project, so I thought why not give it a try for my own blog. There are some very good <a href=\"https:\/\/www.rias.be\/blog\/deploying-a-statamic-3-site-to-netlify\">blog post<\/a> by Rias about running Statamic 2 and 3 on Netlify that I took as a base.<\/p>\n<h2 id=\"why-not-netlify\" class=\"group\">\n Why not Netlify<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#why-not-netlify\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>When converting my blog from Hugo to Statamic and changing settings on Netlify I ran into bigger problems getting all the php composer packages installed in Netlify. The <code>Circular call to script handler 'post-package-install' detected<\/code> error gave me a bad day and I eventually found others had issues (<a href=\"https:\/\/github.com\/pixelfear\/composer-dist-plugin\/issues\/4\">Github issue<\/a>) with this as well. While it seemed to be a problem with Composer 2 in my case I just wasn&rsquo;t able to get this fixed. Even an update from Composer 2.0.7 to 2.0.8 did not help. Unfortunately you cannot specify the version of Composer running when Netlify builds your blog.<\/p>\n<h2 id=\"use-vercel\" class=\"group\">\n Use Vercel<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#use-vercel\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p><a href=\"https:\/\/vercel.com\/\">Vercel<\/a>, which is the former <a href=\"https:\/\/vercel.com\/blog\/zeit-is-now-vercel\">ZEIT<\/a>, is an equally opponent to Netlify build JAMSTACK application. There are already huge comparisons between these two (<a href=\"https:\/\/www.google.com\/search?q=vercel+netlify\">Google<\/a>) so I won&rsquo;t bother you with this.<\/p>\n<h3 id=\"statamic-ssg--vercel-settings\" class=\"group\">\n Statamic SSG &amp; Vercel settings<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#statamic-ssg--vercel-settings\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>You need to use Statamic SSG package to publish your site on Vercel. There is a <a href=\"https:\/\/github.com\/statamic\/ssg#deploy-to-vercel\">separate section<\/a> describing which steps to take, including a build script, and what settings you need. Just fantastic. Additionally I found some more settings can be set using a <code>vercel.json<\/code> (<a href=\"https:\/\/github.com\/codedge\/codedge.de-statamic\/blob\/master\/vercel.json\">see mine<\/a>) in your root project folder:<\/p>\n<ul>\n<li>Setting the correct headers for your sitemap and feed<\/li>\n<li>Deny any iframe embedding of your content<\/li>\n<li>Set cache controls for your assets<\/li>\n<li>Disable the now bot comments on Github for your commits<\/li>\n<\/ul>\n<h3 id=\"some-extras\" class=\"group\">\n Some extras<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#some-extras\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>When switching to Statamic I found a couple of things missing:<\/p>\n<ul>\n<li>a sitemap<\/li>\n<li>an atom feed<\/li>\n<li>a 404 page.<\/li>\n<\/ul>\n<p><strong>Create a feed<\/strong><\/p>\n<p>Create a new route in your <code>routes\/web.php<\/code> under which your feed should be reachable. In my case the feed can be found under <code>\/feed<\/code>:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-php\" data-lang=\"php\"><span style=\"display:flex;\"><span><span style=\"color:#89dceb;font-weight:bold\">&lt;?<\/span>php\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">use<\/span> Illuminate\\Support\\Facades\\Route;\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">\/*\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">|--------------------------------------------------------------------------\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">| Web Routes\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">|--------------------------------------------------------------------------\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">|\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">| Here is where you can register web routes for your application. These\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">| routes are loaded by the RouteServiceProvider within a group which\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">| contains the &#34;web&#34; middleware group. Now create something great!\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">|\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">*\/<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span>Route<span style=\"color:#89dceb;font-weight:bold\">::<\/span><span style=\"color:#89b4fa\">statamic<\/span>(<span style=\"color:#a6e3a1\">&#39;feed&#39;<\/span>, <span style=\"color:#a6e3a1\">&#39;layouts\/feed&#39;<\/span>, [\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#39;layout&#39;<\/span> <span style=\"color:#89dceb;font-weight:bold\">=&gt;<\/span> <span style=\"color:#a6e3a1\">&#39;layouts\/feed&#39;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#39;content_type&#39;<\/span> <span style=\"color:#89dceb;font-weight:bold\">=&gt;<\/span> <span style=\"color:#a6e3a1\">&#39;atom&#39;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>]);\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Next you need to set up a template in which the feed is generated. So I created a file at <code>resource\/views\/layout\/feed.antlers.html<\/code> (<a href=\"https:\/\/github.com\/codedge\/codedge.de-statamic\/blob\/master\/resources\/views\/layouts\/feed.antlers.html\">see on Github<\/a>). To let the SSG package of Statamic know, that it needs to generate your feed route, specify this in <code>config\/ssg.php<\/code> in the <code>urls<\/code> section.<\/p>\n<p><strong>Create a sitemap<\/strong><\/p>\n<p>For generating a sitemap I use Spaties laravel-sitemap package. All you need to do is setting up a command that is run when deploying your site. To let the sitemap build on deployment create a script in your <code>composer.json<\/code><\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">9\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-json\" data-lang=\"json\"><span style=\"display:flex;\"><span><span style=\"color:#a6e3a1\">&#34;scripts&#34;<\/span><span style=\"color:#f38ba8\">:<\/span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;build&#34;<\/span>: [\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#34;yarn production&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#34;@php -r \\&#34;file_exists(&#39;.env&#39;) || copy(&#39;.env.example&#39;, &#39;.env&#39;);\\&#34;&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#34;@php artisan key:generate&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#34;@php please ssg:generate&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#a6e3a1\">&#34;@php artisan sitemap&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> ]\n<\/span><\/span><span style=\"display:flex;\"><span>}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>and let <code>composer run build<\/code> run when deploying to Vercel. This should already be included in the build script from Statamics SSG package.<\/p>\n<h2 id=\"the-404-page\" class=\"group\">\n The 404 page<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#the-404-page\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Vercel, by default, checks for <code>404.html<\/code> inside the output folder of the application.<\/p>\n<p>So the easiest solution is to just create a <code>404.html<\/code> in your <code>public<\/code> folder and let this file being copied to the final static folder by setting it in your <code>config\/ssg.php<\/code> config file.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-php\" data-lang=\"php\"><span style=\"display:flex;\"><span><span style=\"color:#a6e3a1\">&#39;copy&#39;<\/span> <span style=\"color:#89dceb;font-weight:bold\">=&gt;<\/span> [\n<\/span><\/span><span style=\"display:flex;\"><span> public_path(<span style=\"color:#a6e3a1\">&#39;css&#39;<\/span>) <span style=\"color:#89dceb;font-weight:bold\">=&gt;<\/span> <span style=\"color:#a6e3a1\">&#39;css&#39;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> public_path(<span style=\"color:#a6e3a1\">&#39;js&#39;<\/span>) <span style=\"color:#89dceb;font-weight:bold\">=&gt;<\/span> <span style=\"color:#a6e3a1\">&#39;js&#39;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ Other files and folders\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> public_path(<span style=\"color:#a6e3a1\">&#39;404.html&#39;<\/span>) <span style=\"color:#89dceb;font-weight:bold\">=&gt;<\/span> <span style=\"color:#a6e3a1\">&#39;404.html&#39;<\/span>, <span style=\"color:#6c7086;font-style:italic\">\/\/ &lt;-- we need this\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span>],\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>The bad thing with this is, you cannot use the already existing layout as it just skips the whole Statamic magic. I hope the <a href=\"https:\/\/github.com\/statamic\/ssg\/issues\/33\">open issue<\/a> in the <a href=\"https:\/\/github.com\/statamic\/ssg\">SSG repository<\/a> gets fixed more or less soon, so that Statamic is automatically emitting a <code>404.html<\/code>.<\/p>"},{"title":"Render images in Statamics Bard","link":"https:\/\/www.codedge.de\/posts\/render-images-in-statamics-bard\/","pubDate":"Tue, 01 Dec 2020 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/render-images-in-statamics-bard\/","description":"<p>The <a href=\"https:\/\/statamic.dev\/fieldtypes\/bard\">Bard fieldtype<\/a> is a beautiful idea to create long texts containing images, code samples - basically any sort of content. While I was creating my blog I was not sure how to extract images from the Bard field.<\/p>\n<p>Thanks to the <a href=\"https:\/\/statamic.dev\/tags\/glide\">Glide tag<\/a> you can just simply pass the field of your image and it automatically outputs the proper url. My image field is a <a href=\"https:\/\/statamic.dev\/fieldtypes\/bard#sets\">set<\/a> called image.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">sets<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">image<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">display<\/span>: Image\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">fields<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> - <span style=\"color:#cba6f7\">handle<\/span>: image\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">field<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">mode<\/span>: grid\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">restrict<\/span>: <span style=\"color:#fab387\">false<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">allow_uploads<\/span>: <span style=\"color:#fab387\">true<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">display<\/span>: Image\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">type<\/span>: assets\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">icon<\/span>: assets\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">listable<\/span>: hidden\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">container<\/span>: blog_images\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">max_files<\/span>: <span style=\"color:#fab387\">1<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>In your Antlers template for the images just write<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-html\" data-lang=\"html\"><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">div<\/span> <span style=\"color:#89b4fa\">class<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;flex justify-center mb-2 max-w-4xl mx-auto&#34;<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span> {{ image }} {{ glide:image tag=&#34;true&#34; }} {{ \/image }}\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;\/<span style=\"color:#cba6f7\">div<\/span>&gt;\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><h2 id=\"responsive-images\" class=\"group\">\n Responsive images<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#responsive-images\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>For generating responsive images you can use the excellent <a href=\"https:\/\/statamic.com\/addons\/spatie\/responsive-images\">Statamic Responsive Images<\/a> addon provided by <a href=\"https:\/\/spatie.be\/\">Spatie<\/a>. With this the above snippet changes to that:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-html\" data-lang=\"html\"><span style=\"display:flex;\"><span>&lt;<span style=\"color:#cba6f7\">div<\/span> <span style=\"color:#89b4fa\">class<\/span><span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;flex justify-center mb-2 max-w-4xl mx-auto&#34;<\/span>&gt;\n<\/span><\/span><span style=\"display:flex;\"><span> {{ image }} {{ responsive:image tag=&#34;true&#34; }} {{ \/image }}\n<\/span><\/span><span style=\"display:flex;\"><span>&lt;\/<span style=\"color:#cba6f7\">div<\/span>&gt;\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>This generates a tag with <code>srcset<\/code> to render images for differrent breakpoints.<\/p>"},{"title":"Create proper database dumps for Magento 2","link":"https:\/\/www.codedge.de\/posts\/create-proper-magento-database-dumps\/","pubDate":"Thu, 26 Nov 2020 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/create-proper-magento-database-dumps\/","description":"<p>When developing a Magento2 you might be needing database dumps from time to time from the production system. Normally you either dump the complete database - which, to me, bring a lot of negative side effects.<\/p>\n<ul>\n<li><strong>Size<\/strong>: Depending on the shop and the logging settings, the tables can grow up to multiple gigabytes.<\/li>\n<li><strong>Sensitive (customer) data<\/strong>: Getting all the (hashed) passwords, addresses and names from customers does not feel like a good idea. Depending on the companies compliance guideline you may not even allowed to have them.<\/li>\n<li><strong>Old\/outdated data<\/strong>: Tables with logs or reports that are not necessary for your local development.<\/li>\n<\/ul>\n<p>Instead of now writing your own script to dump only the files you need and exclude those, you don&rsquo;t want, just use the fantastic <a href=\"https:\/\/github.com\/netz98\/n98-magerun\">n98-magerun2<\/a>. To dump a database without logs, sessions, trade data, admin users and index <code>idx<\/code> tables use:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-bash\" data-lang=\"bash\"><span style=\"display:flex;\"><span>n98-magerun.phar db:dump --strip<span style=\"color:#89dceb;font-weight:bold\">=<\/span><span style=\"color:#a6e3a1\">&#34;@development @idx&#34;<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Check the <a href=\"https:\/\/github.com\/netz98\/n98-magerun\/wiki\/Stripped-Database-Dumps\">extended documentation<\/a> for further groups and how to create your own groups.<\/p>"},{"title":"Run a PHP application on AWS Fargate","link":"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/","pubDate":"Sun, 19 Apr 2020 00:00:00 +0200","guid":"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/","description":"<p>Following the trend of serverless, all that hype (or not?) I was looking through the AWS services offered and stumbled upon <a href=\"https:\/\/aws.amazon.com\/fargate\/\">AWS Fargate<\/a>, a service that lets you run containerized applications on either Amazon Elastic Container Service (ECS) or <a href=\"https:\/\/aws.amazon.com\/eks\/\">Amazon Elastic Kubernetes Services (EKS)<\/a>.<\/p>\n<p>For the tooling (development and deployment) of our PHP application I&rsquo;d like to stick to probably the widely adopted tools, like:<\/p>\n<ul>\n<li><strong>Deployment<\/strong>: Github including Github Actions<\/li>\n<li><strong>Infrastructure<\/strong>: Elastic Container Service (ECS) to run our containers on\nDocker container hosted on Amazon Elastic Container Registry (ECR)<\/li>\n<li><strong>Database<\/strong>: we&rsquo;ll be using Amazon RDS<\/li>\n<li><strong>Logging<\/strong>: Amazon Cloudwatch<\/li>\n<\/ul>\n<p>Of course, you&rsquo;re not bound to this tooling. If you want to use another external database hosted somewhere else than on Amazon, feel free to do. The same applies to logging. You&rsquo;ve got a working <a href=\"https:\/\/www.graylog.org\/\">Greylog<\/a> up and running then you&rsquo;re free to use that. These things are not mandatory for AWS Fargate. I just decided to use that for convenience reasons.<\/p>\n<p>If you want to know about pricing of this setup by now, let&rsquo;s leave it with what advocates usually say: It depends! \ud83d\ude09 To get a general feeling have look at the costs section at the end of this article. A lot of the (further) costs depend on the pricing. For this project\/experiment I use Laravel 7. Of course, you can use any framework you&rsquo;d like to run this application. Just make sure you adjust certain paths for building the Docker images.<\/p>\n<div class=\"notice info\" >\n <p class=\"notice-title\">\n <span class=\"icon-notice baseline\">\n <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"92 59.5 300 300\">\n <path d=\"M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z\"\/>\n<\/svg>\n\n <\/span>Info<\/p><p><strong>Grab the code<\/strong>:\nAll files can be found in this <a href=\"https:\/\/github.com\/codedge\/aws-fargate-with-php\">Github repository<\/a>. Feel free to open issues and PRs if you spot anything wrong.<\/p><\/div>\n\n<p>The final directory layout:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">21\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">22\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">23\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">24\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">25\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">26\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">27\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">28\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">29\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">30\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">31\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">32\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">33\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .dockerignore\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .editorconfig\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .env\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .env.example\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .git\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .gitattributes\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .github\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .gitignore\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .idea\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 .styleci.yml\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 Dockerfile\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 app\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 artisan\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 bootstrap\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 composer.json\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 composer.lock\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 config\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 database\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 docker\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 docker-compose.yml\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 node_modules\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 package.json\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 phpunit.xml\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 public\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 resources\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 routes\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 server.php\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 storage\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 task-definition.json\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 tests\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 vendor\n<\/span><\/span><span style=\"display:flex;\"><span>\u251c\u2500\u2500 webpack.mix.js\n<\/span><\/span><span style=\"display:flex;\"><span>\u2514\u2500\u2500 yarn.lock\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Let&rsquo;s get started!<\/p>\n<h2 id=\"preparing-the-docker-images\" class=\"group\">\n Preparing the Docker images<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#preparing-the-docker-images\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>To provide the complete application to AWS Fargate, we&rsquo;ll split it into three containers:<\/p>\n<ul>\n<li>Nginx<\/li>\n<li>PHP-FPM<\/li>\n<li>NodeJS<\/li>\n<\/ul>\n<p>For the database we don&rsquo;t create a separate container as we need a stateful solution. We&rsquo;ll rely on a <a href=\"https:\/\/docs.docker.com\/develop\/develop-images\/multistage-build\/\">multi-stage<\/a> build as well as on <code>alpine<\/code> images only for our Docker images to keep the image sizes extremely small.<\/p>\n<p>You can find some comparison and further reading on Alpine images in this <a href=\"https:\/\/nickjanetakis.com\/blog\/the-3-biggest-wins-when-using-alpine-as-a-base-docker-image\">blog post<\/a> or on the Alpine docker page.\nHere is an example just to demonstrate the basic usage for our multi-stage build. You can find the full Dockerfile in the <a href=\"https:\/\/github.com\/codedge\/aws-fargate-with-laravel\/blob\/master\/Dockerfile\">repository<\/a>.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">20\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">21\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">22\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">23\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">24\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">25\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">26\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">27\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-Dockerfile\" data-lang=\"Dockerfile\"><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\">### PHP<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#6c7086;font-style:italic\"># Use the php7.4-fpm-alpine image and aliasing it as &#39;laravelapp_php&#39;<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#cba6f7\">FROM<\/span> <span style=\"color:#a6e3a1\">php:7.4-fpm-alpine<\/span> <span style=\"color:#cba6f7\">AS<\/span> <span style=\"color:#a6e3a1\">laravelapp_php<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#6c7086;font-style:italic\"># Your instruction to build the image for php<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#6c7086;font-style:italic\"># Then you need to copy the items needed from another image to this. In our case we copy the &#39;composer&#39; executable<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#cba6f7\">COPY<\/span> --from<span style=\"color:#89dceb;font-weight:bold\">=<\/span>composer:latest \/usr\/bin\/composer \/usr\/bin\/composer<span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#6c7086;font-style:italic\"># copy everything, excluding the one from .dockerignore file<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#cba6f7\">COPY<\/span> . .\/<span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#cba6f7\">RUN<\/span> <span style=\"color:#89dceb\">set<\/span> -eux; <span style=\"color:#89b4fa\">\\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\"><\/span> mkdir -p storage\/logs storage\/framework bootstrap\/cache; <span style=\"color:#89b4fa\">\\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\"><\/span> composer install --prefer-dist --no-progress --no-suggest --optimize-autoloader; <span style=\"color:#89b4fa\">\\\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#89b4fa\"><\/span> composer clear-cache<span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#6c7086;font-style:italic\"># ... and so on ...<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#6c7086;font-style:italic\">### NGINX<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#cba6f7\">FROM<\/span> <span style=\"color:#a6e3a1\">nginx:1.17-alpine<\/span> <span style=\"color:#cba6f7\">AS<\/span> <span style=\"color:#a6e3a1\">laravelapp_nginx<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#6c7086;font-style:italic\"># Copy our files from the laravelapp_php image to this one here<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#6c7086;font-style:italic\"># Nginx only needs to have the files in &#39;public\/&#39;. The other php files to only exist in the php image<\/span><span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#f38ba8\"><\/span><span style=\"color:#cba6f7\">COPY<\/span> --from<span style=\"color:#89dceb;font-weight:bold\">=<\/span>laravelapp_php \/srv\/laravelapp\/public public\/<span style=\"color:#f38ba8\">\n<\/span><\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Due to the fact that we only hold the files needed for the containers we keep to size of the images relatively small. For the php image, the size mainly depends on the modules needed as we need to include the build and development files for creating the extensions. This might increase our image size.<\/p>\n<p>For my setup I ended with these sizes:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span>REPOSITORY TAG IMAGE ID CREATED SIZE\n<\/span><\/span><span style=\"display:flex;\"><span>laravelapp-nginx latest 252bcd38e8aa <span style=\"color:#fab387\">42<\/span> hours ago 19.8MB\n<\/span><\/span><span style=\"display:flex;\"><span>laravelapp-php-fpm latest 9c2afe650880 <span style=\"color:#fab387\">43<\/span> hours ago 220MB\n<\/span><\/span><span style=\"display:flex;\"><span>laravelapp-nodejs latest 9a0b33434754 <span style=\"color:#fab387\">46<\/span> hours ago\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><h3 id=\"container-orchestration\" class=\"group\">\n Container orchestration<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#container-orchestration\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Although included in the project and supported by AWS Fargate, <a href=\"https:\/\/docs.docker.com\/compose\/\">Docker compose<\/a> is not going to be used in our AWS deployment. For orchestration of the containers we are going to use Amazons ECS tasks definition in which you can link containers. I will come to that a bit later when talking about the task definition. You can use the the docker-compose file for local testing of the containers. In there you can see how we addressed to different stages of the Docker image - using <a href=\"https:\/\/docs.docker.com\/compose\/compose-file\/#target\"><code>target<\/code><\/a>, although we have only one Dockerfile.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">7\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">version<\/span>: <span style=\"color:#a6e3a1\">&#34;3.4&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span>\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">services<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">php-fpm<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">build<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">context<\/span>: .\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">target<\/span>: laravelapp_php\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Through the <code>depends_on<\/code> item the containers are linked.\nDo not use <a href=\"https:\/\/docs.docker.com\/compose\/compose-file\/#links\">links<\/a>. Using links in your docker-compose.yml is considered <a href=\"https:\/\/docs.docker.com\/compose\/compose-file\/#links\">deprecated<\/a> and being removed more or less soon. Either use <code>depends_on<\/code> or create a <a href=\"https:\/\/docs.docker.com\/compose\/networking\/\">user-defined network<\/a>. For local testing you just need to run <code>docker-compose up<\/code>. Afterwards you&rsquo;ll find the three images, linked, up and running. The push to the repository is part of our deployment pipeline which is our next topic.<\/p>\n<h2 id=\"deployment-with-github-actions\" class=\"group\">\n Deployment with Github Actions<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#deployment-with-github-actions\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>For deployment we are going to use <a href=\"https:\/\/github.com\/features\/actions\">Github Actions<\/a> to create the Docker images, push them to a Docker registry (ECR in our case), creating a deployment task for ECS to pick up the images and spin up a new service that runs our three containers on ECS.<\/p>\n<h3 id=\"the-workflow\" class=\"group\">\n The workflow<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#the-workflow\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Most of you are already familiar with so we are going to step right into our workflow file at <code>.github\/workflows\/deploy.yml<\/code>. To make the workflow work you need to register secrets in Github for your AWS key, AWS secret key and AWS region. Furthermore you need to adjust the <code>ECR_REPOSITORY<\/code> names (3 in total) according to the repositories you have created in AWS. For that, we quickly switch to our ECR console and create three repositories.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecr.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecr_hu_7c8c9c10608b14cb.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecr_hu_d0984f4aad11e2e3.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecr_hu_ab507b51ad334060.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecr_hu_f3ef0d68e46157d3.webp\">\n\n <img class=\"img\" alt=\"AWS ECR\" title=\"AWS ECR\"\n src=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecr.png\" loading=\"lazy\" decoding=\"async\"\n height=\"710\" width=\"2676\">\n <\/picture>\n <\/a>\n \n <figcaption>\n AWS ECR\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<h3 id=\"tag-immutability\" class=\"group\">\n Tag immutability<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#tag-immutability\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>This feature is supported since <a href=\"https:\/\/aws.amazon.com\/about-aws\/whats-new\/2019\/07\/amazon-ecr-now-supports-immutable-image-tags\/\">mid of 2019<\/a> and prevents tags from being overwritten. As we&rsquo;re tagging the images with <code>latest<\/code> and wanting this tag to reflect the latest changes we set this to <code>disabled<\/code>.<\/p>\n<h3 id=\"scan-on-push\" class=\"group\">\n Scan on push<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#scan-on-push\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p><a href=\"https:\/\/docs.aws.amazon.com\/AmazonECR\/latest\/userguide\/image-scanning.html\">Image scanning<\/a> helps in identifying software vulnerabilities in your container images. We are going to use this here. You can certainly disable it too, if you don&rsquo;t like it for whatever reasons.<\/p>\n<p>After you created the secret in your Github repository, set up the repositories on AWS ECR, adjusted the names for the repositories in your workflow let&rsquo;s quickly have a look at each of the workflow steps:<\/p>\n<ul>\n<li><strong>Checkout<\/strong>: Checkout your repository from Github<\/li>\n<li><strong>Configure AWS credentials<\/strong>: Configure AWS credential environment variables for use in other GitHub Actions<\/li>\n<li><strong>Login to Amazon ECR<\/strong>: Log into Amazon ECR with the local Docker client<\/li>\n<li><strong>Build, tag, push image: nginx<\/strong>: Build the Docker image for nginx and pushes it to Amazon ECR<\/li>\n<li><strong>Build, tag, push image: php-fpm<\/strong>: Build the Docker image for php-fpm and pushes it to Amazon ECR<\/li>\n<li><strong>Build, tag, push image<\/strong>: nodejs: Build the Docker image for nodejs and pushes it to Amazon ECR<\/li>\n<li><strong>Render task definition: nginx<\/strong>: Renders the final repository URL including the name of repository into the task-definition.json file. We&rsquo;ll come to that in the next topic.<\/li>\n<li><strong>Render task definition: php-fpm<\/strong>: Renders the final repository URL including the name of repository into the task-definition.json file.<\/li>\n<li><strong>Render task definition: nodejs<\/strong>: Renders the final repository URL including the name of repository into the task-definition.json file.<\/li>\n<li><strong>Deploy Amazon ECS task definition<\/strong>: A task definition is required to run Docker containers in Amazon ECS. You define your containers, its hardware resources, inter-container connections as well as host connections, where to send logs to, and many more. See the next section about this topic. But first let&rsquo;s check for two important settings, <code>service<\/code> and <code>cluster<\/code>.\n<ul>\n<li><code>cluster<\/code> is the name you are going to choose in Amazon ECS.<\/li>\n<li><code>service<\/code> is the name for the service that picks up the task and deploys it into the cluster.<\/li>\n<\/ul>\n<\/li>\n<li><strong>Logout of Amazon ECR<\/strong>: Log out from Amazon ECR and erase any credentials connected with it<\/li>\n<\/ul>\n<h3 id=\"task-definition-for-ecs\" class=\"group\">\n Task definition for ECS<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#task-definition-for-ecs\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>In ECS, the basic unit of a deployment is a task, a logical construct that models one or more containers. This means that the ECS APIs operate on tasks rather than individual containers. In ECS, you can\u2019t run a container: rather, you run a task, which, in turns, run your container(s). A task contains one or more containers.<\/p>\n<p>\n\n\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/containers_and_tasks.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/containers_and_tasks_hu_f3d1390da23c38c6.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/containers_and_tasks_hu_97e963e23b5e3efd.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/containers_and_tasks_hu_1d4ff96e2b6d2eca.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/containers_and_tasks_hu_46f5a6b8d01d9d2.webp\">\n\n <img class=\"img\" alt=\"containers_and_tasks.png\" title=\"containers_and_tasks.png\"\n src=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/containers_and_tasks.png\" loading=\"lazy\" decoding=\"async\"\n height=\"323\" width=\"1239\">\n <\/picture>\n <\/a>\n \n <figcaption>\n containers_and_tasks.png\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<p>In our workflow the steps 7, 8 and 9 are responsible to adjust the <a href=\"https:\/\/github.com\/codedge\/aws-fargate-with-laravel\/blob\/master\/task-definition.json\"><code>task-definition.json<\/code><\/a> file. This file can be compared to the <code>docker-compose.yml<\/code> or any other orchestration file you use to connect your Docker containers. One special thing here is the following line appearing in each of step 7, 8 and 9:<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">task-definition<\/span>: task-definition.json\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">container-name<\/span>: nginx\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">image<\/span>: ${{ steps.build-image-nginx.outputs.image }}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">task-definition<\/span>: ${{ steps.task-def-nginx.outputs.task-definition }}\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">container-name<\/span>: php-fpm\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">image<\/span>: ${{ steps.build-image-php-fpm.outputs.image }}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">task-definition<\/span>: ${{ steps.task-def-php-fpm.outputs.task-definition }}\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">container-name<\/span>: nodejs\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#cba6f7\">image<\/span>: ${{ steps.build-image-nodejs.outputs.image }}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>What it does in 8 and 9 is, that it takes the former out and exchange the <code>image<\/code> field inside. In step 7 it uses the <code>task-definition.json<\/code> as it is. This is needed to iteratively inserting the image used from our Docker registry and finally pushing the complete task definition in step 10.\nMoving on to the task-definition.json file itself. There is a whole documentation sections on this file <a href=\"https:\/\/docs.aws.amazon.com\/AmazonECS\/latest\/developerguide\/task_definitions.html\">AWS docs<\/a> page. I&rsquo;ll continue with the parts that are relevant for us here.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">15\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">16\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">17\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">18\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">19\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-json\" data-lang=\"json\"><span style=\"display:flex;\"><span>{\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;family&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;laravel-backend-app&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;containerDefinitions&#34;<\/span>: [\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ container 1, nginx\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> },\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ container 2, php-fpm\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> },\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ container 3, nodejs\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> }\n<\/span><\/span><span style=\"display:flex;\"><span> ],\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;executionRoleArn&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;ecsTaskExecutionRole&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;cpu&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;2048&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;memory&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;4096&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;networkMode&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;awsvpc&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;requiresCompatibilities&#34;<\/span>: [<span style=\"color:#a6e3a1\">&#34;FARGATE&#34;<\/span>]\n<\/span><\/span><span style=\"display:flex;\"><span>}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><p>Let&rsquo;s go through the file, but starting at the end:<\/p>\n<ul>\n<li><strong>requiresCompatibilities<\/strong>: This needs to be set to FARGATE. Otherwise ECS won&rsquo;t recognize it properly.<\/li>\n<li><strong>networkMode<\/strong>: This is set to awsvpc, so every task that is launched from that task definition gets its own elastic network interface (ENI) and a primary private IP address. That makes it possible to call services and applications as if they would be in one system (not in distributed containers).<br>\nExample for nginx calling php-fpm: <code>fastcgi_pass 127.0.0.1:9000;<\/code> If we would orchestrate with Docker Compose we normally call a container by its name. So the above statement would probably be <code>fastcgi_pass php:9000;<\/code>.<\/li>\n<li><strong>cpu<\/strong>: The cpu value can be expressed in CPU units or vCPUs in a task definition but is converted to an integer indicating the CPU units when the task definition is registered.<\/li>\n<li><strong>memory<\/strong>: The memory value can be expressed in MiB or GB in a task definition but is converted to an integer indicating the MiB when the task definition is registered.\nBoth values, <code>cpu<\/code> and <code>memory<\/code> can be defined for each container separately or for the complete task. In this sample application here, I defined the values for the complete task. This runs just fine. And remember, you can change (scale) this as you need. This is just a point to start from.<\/li>\n<li><strong>executionRoleArn<\/strong>: This is connected to permissions on AWS. We leave this to the value <code>ecsTaskExecutionRole<\/code>. I&rsquo;ll come to this in the section about configuring AWS Fargate and its roles.<\/li>\n<li><strong>family<\/strong>: This is the name for our task that can be freely choosen. So you can deploy multiple <code>laravel-backend-app<\/code> if you like and do balancing and stuff. It is just common name for a set of containers, in our case here for three.<\/li>\n<li><strong>containerDefinitions<\/strong>: Here comes the fun part&hellip; the containers. I am going to summarize the important things you&rsquo;ll encounter in inside the <code>task-definition.json<\/code>:<br>\n<code>nginx<\/code>: Open port 80 to host to be accessible from outside<br>\n<code>php-fpm<\/code>: Open port 9000 only for inter-container communication to be accessible from nginx, Secrets and environment variable like keys, settings for debugging, env to be used are properly set<br>\n<code>nodejs<\/code>: nothing special<\/li>\n<\/ul>\n<p>All containers have <code>essential<\/code> set to <code>true<\/code>. This means, that if one container fails or stops for any reason, all other containers that are part of the task are stopped. Next is all the configuration about AWS Fargate and all the connected services we are going to use.<\/p>\n<h2 id=\"aws-fargate\" class=\"group\">\n AWS Fargate<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#aws-fargate\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>AWS Fargate cannot be configured directly as it is more an underlying technology to run serverless applications on Amazon AWS. In the next chapters we are going to step into each part that needs configuration to get the whole Laravel application running.<\/p>\n<h3 id=\"security-iams-roles-and-permissions\" class=\"group\">\n Security: IAMs, roles and permissions<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#security-iams-roles-and-permissions\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Talking about security on AWS could fill an entire series of posts. I&rsquo;ll keep this to a minumum where I&rsquo;d personally think this is a reasonable way to operate an application.<\/p>\n<p>Separate user and group for your application In your Identity and Access Management (IAM) create a new user called <code>laravelapp<\/code> that is the one who is allowed to run all tasks around your application.<\/p>\n<p>Assign and manage permissions via a group, f. ex. <code>LaravelAppManageServices<\/code>. Make the user <code>laravelapp<\/code> member of this group and then assign permissions to this group instead of directly to the user.<\/p>\n<p>Roles for ECR and ECS By default the newly created user <code>laravelapp<\/code> has no rights to execute any operation on ECR or ECS. In our case, this user need to be able to push images to ECR or to run services on ECS to execute our task to spin up the container. For that we need to attach to two policies to our group:<\/p>\n<ul>\n<li>AmazonEC2ContainerRegistryFullAccess<\/li>\n<li>AmazonECS_FullAccess<\/li>\n<\/ul>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_groups.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_groups_hu_98531252f6850f79.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_groups_hu_b9de4078ca627089.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_groups_hu_9ec6a38f3154ff55.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_groups_hu_2f82669bf303b7b8.webp\">\n\n <img class=\"img\" alt=\"IAM groups\" title=\"IAM groups\"\n src=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_groups.png\" loading=\"lazy\" decoding=\"async\"\n height=\"1438\" width=\"2768\">\n <\/picture>\n <\/a>\n \n <figcaption>\n IAM groups\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<p>Surely we would need to tighten the permissions a bit later on. I&rsquo;ll get to that in a separate post.<\/p>\n<p><strong>Execution role<\/strong><\/p>\n<p>From the <a href=\"https:\/\/docs.aws.amazon.com\/AmazonECS\/latest\/developerguide\/task_execution_IAM_role.html\">AWS docs<\/a><\/p>\n<blockquote>\n<p>The Amazon ECS container agent, and the Fargate agent for your Fargate tasks, make calls to the Amazon ECS API on your behalf. The agent requires an IAM role for the service to know that the agent belongs to you. This IAM role is referred to as a task execution IAM role.<\/p>\n<\/blockquote>\n<p>So the, create a new role called <code>ecsTaskExecutionRole<\/code> and attach the following policies:<\/p>\n<ul>\n<li>AmazonECSTaskExecutionRolePolicy<\/li>\n<li>AmazonSSMReadOnlyAccess: we are going to need that to read our environment variables from AWS Systems Manager Parameter Store<\/li>\n<\/ul>\n<p>\n\n\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_ecstaskexecutionrole.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_ecstaskexecutionrole_hu_89e49a0afbc0682.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_ecstaskexecutionrole_hu_6cd3f8acc87eab6f.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_ecstaskexecutionrole_hu_8dbb956137fdf22.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_ecstaskexecutionrole_hu_ee9b6e1de5bee5d6.webp\">\n\n <img class=\"img\" alt=\"iam_ecstaskexecutionrole.png\" title=\"iam_ecstaskexecutionrole.png\"\n src=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/iam_ecstaskexecutionrole.png\" loading=\"lazy\" decoding=\"async\"\n height=\"1432\" width=\"2166\">\n <\/picture>\n <\/a>\n \n <figcaption>\n iam_ecstaskexecutionrole.png\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<div class=\"notice tip\" >\n <p class=\"notice-title\">\n <span class=\"icon-notice baseline\">\n <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"300.5 134 300 300\">\n <path d=\"M551.281 252.36c0-3.32-1.172-6.641-3.515-8.985l-17.774-17.578c-2.344-2.344-5.469-3.711-8.789-3.711-3.32 0-6.445 1.367-8.789 3.71l-79.687 79.493-44.141-44.14c-2.344-2.344-5.469-3.712-8.79-3.712-3.32 0-6.444 1.368-8.788 3.711l-17.774 17.579c-2.343 2.343-3.515 5.664-3.515 8.984 0 3.32 1.172 6.445 3.515 8.789l70.704 70.703c2.343 2.344 5.664 3.711 8.789 3.711 3.32 0 6.64-1.367 8.984-3.71l106.055-106.056c2.343-2.343 3.515-5.468 3.515-8.789ZM600.5 284c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z\"\/>\n<\/svg>\n\n <\/span>Tip<\/p><p>The name of this role <code>ecsExecutionRole<\/code> needs to match the value in our workflow.<\/p><\/div>\n\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-shell\" data-lang=\"shell\"><span style=\"display:flex;\"><span><span style=\"color:#a6e3a1\">&#34;executionRoleArn&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;ecsTaskExecutionRole&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#a6e3a1\">&#34;cpu&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;2048&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#a6e3a1\">&#34;memory&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;4096&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#a6e3a1\">&#34;networkMode&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;awsvpc&#34;<\/span>,\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><h3 id=\"create-the-ecs-cluster\" class=\"group\">\n Create the ECS cluster<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-the-ecs-cluster\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Head over to your AWS Management Console, open Services, type ECS and click on Elastic Container Service.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_dashboard_ecs.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_dashboard_ecs_hu_ff61b57c84d74ca7.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_dashboard_ecs_hu_6ff73c4c2db27c2c.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_dashboard_ecs_hu_f27da7dd869391f4.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_dashboard_ecs_hu_510b2fefd23be442.webp\">\n\n <img class=\"img\" alt=\"AWS Dashboard ECS\" title=\"AWS Dashboard ECS\"\n src=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_dashboard_ecs.png\" loading=\"lazy\" decoding=\"async\"\n height=\"377\" width=\"2666\">\n <\/picture>\n <\/a>\n \n <figcaption>\n AWS Dashboard ECS\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<p>On the left side menu click on <code>Amazon ECS &gt; Clusters<\/code> and hit the <em>Create Cluster<\/em> button.<\/p>\n<p>\n\n\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecs_create_cluster.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecs_create_cluster_hu_7746fbdce72c162e.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecs_create_cluster_hu_9d09cefe5b718846.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecs_create_cluster_hu_57ed6450f40b7d08.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecs_create_cluster_hu_b8102e8f9b93a98f.webp\">\n\n <img class=\"img\" alt=\"aws_ecs_create_cluster.png\" title=\"aws_ecs_create_cluster.png\"\n src=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/aws_ecs_create_cluster.png\" loading=\"lazy\" decoding=\"async\"\n height=\"542\" width=\"2688\">\n <\/picture>\n <\/a>\n \n <figcaption>\n aws_ecs_create_cluster.png\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<h4 id=\"create-service-step-1\" class=\"group\">\n Create service: Step 1<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-service-step-1\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><ul>\n<li>Launch type: Fargate<\/li>\n<li>Task definition: This is prepopulated with the <code>family<\/code> name in our <code>task-definition.json<\/code>.<\/li>\n<\/ul>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-json\" data-lang=\"json\"><span style=\"display:flex;\"><span>{\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;family&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;laravel-backend-app&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;containerDefinitions&#34;<\/span>: [\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#6c7086;font-style:italic\">\/\/ container 1, nginx\n<\/span><\/span><\/span><span style=\"display:flex;\"><span><span style=\"color:#6c7086;font-style:italic\"><\/span> },\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><ul>\n<li>Service name: This should match the service name from our workflow file.<\/li>\n<\/ul>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">6\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-yaml\" data-lang=\"yaml\"><span style=\"display:flex;\"><span>- <span style=\"color:#cba6f7\">name<\/span>: <span style=\"color:#a6e3a1\">&#34;Deploy Amazon ECS task definition&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">uses<\/span>: aws-actions\/amazon-ecs-deploy-task-definition@v1\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">with<\/span>:\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">task-definition<\/span>: ${{ steps.task-def-nodejs.outputs.task-definition }}\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">service<\/span>: laravelapp-backend\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">cluster<\/span>: ${{ secrets.ECS_CLUSTER_NAME }}\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><ul>\n<li>Number of tasks: Leave that to <code>1<\/code> for now. We will not run more than one instance of this service.<\/li>\n<\/ul>\n<h4 id=\"create-service-step-2\" class=\"group\">\n Create service: Step 2<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-service-step-2\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p><strong>Cluster VPC<\/strong>: Make sure you select the correct Virtual Private Cluster (VPC) group that was created together with your cluster. It should be selected automatically.<\/p>\n<p><strong>Subnets<\/strong>: Select the subnet that are unselected, normally two. Load balancers: We will go with None for the moment and come back later to add a Load Balancer to our service.<\/p>\n<p><strong>Service discovery<\/strong>: Disable service discovery as we won&rsquo;t use Amazon Route 53 for this project. If needed you can add this later on, of course.<\/p>\n<h4 id=\"create-service-step-3\" class=\"group\">\n Create service: Step 3<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-service-step-3\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p><strong>Auto-scaling<\/strong>: Skip that. We won&rsquo;t use that.<\/p>\n<h4 id=\"create-service-step-4\" class=\"group\">\n Create service: Step 4<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-service-step-4\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p>Review all your settings and hit <em>Create service<\/em>.<\/p>\n<h3 id=\"environment-variables-and-secrets\" class=\"group\">\n Environment variables and secrets<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#environment-variables-and-secrets\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Managing secrets and\/or environment variables on AWS can be done with either AWS Secrets Manager or with <a href=\"https:\/\/docs.aws.amazon.com\/systems-manager\/latest\/userguide\/systems-manager-parameter-store.html\">AWS Systems Manager Parameter Store<\/a>. I decided to go with the parameter store for one main reason: it is (almost) free of charge.<\/p>\n<ul>\n<li><strong>Parameter store<\/strong>: Free of charge, limit of 10,000 parameters per account<\/li>\n<li><strong>Secrets manager<\/strong>: $0.40 per secret stored and additional $0.05 for 10,000 API calls<\/li>\n<\/ul>\n<p>For those who want to read more about differences and pros and cons of both solutions have a look at this blog post for comparison of both. Whether it is a configuration like <code>APP_DEBUG<\/code> or an actual secret like the <code>APP_KEY<\/code>. Both is going to be stored in the parameter store and injected into the container in our <code>task-defintion.json<\/code>.<\/p>\n<div class=\"highlight\"><div style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\">\n<table style=\"border-spacing:0;padding:0;margin:0;border:0;\"><tr><td style=\"vertical-align:top;padding:0;margin:0;border:0;\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 1\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 2\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 3\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 4\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 5\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 6\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 7\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 8\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\"> 9\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">10\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">11\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">12\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">13\n<\/span><span style=\"white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c\">14\n<\/span><\/code><\/pre><\/td>\n<td style=\"vertical-align:top;padding:0;margin:0;border:0;;width:100%\">\n<pre tabindex=\"0\" style=\"color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"><code class=\"language-json\" data-lang=\"json\"><span style=\"display:flex;\"><span><span style=\"color:#a6e3a1\">&#34;secrets&#34;<\/span><span style=\"color:#f38ba8\">:<\/span> [\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;name&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;APP_ENV&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;valueFrom&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;laravelapp_app_env&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> },\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;name&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;APP_DEBUG&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;valueFrom&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;laravelapp_app_debug&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> },\n<\/span><\/span><span style=\"display:flex;\"><span> {\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;name&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;APP_KEY&#34;<\/span>,\n<\/span><\/span><span style=\"display:flex;\"><span> <span style=\"color:#cba6f7\">&#34;valueFrom&#34;<\/span>: <span style=\"color:#a6e3a1\">&#34;laravelapp_app_key&#34;<\/span>\n<\/span><\/span><span style=\"display:flex;\"><span> }\n<\/span><\/span><span style=\"display:flex;\"><span>]<span style=\"color:#f38ba8\">,<\/span>\n<\/span><\/span><\/code><\/pre><\/td><\/tr><\/table>\n<\/div>\n<\/div><div class=\"notice info\" >\n <p class=\"notice-title\">\n <span class=\"icon-notice baseline\">\n <svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"92 59.5 300 300\">\n <path d=\"M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z\"\/>\n<\/svg>\n\n <\/span>Info<\/p><p>Store your <code>APP_KEY<\/code> as <code>SecureString<\/code> instead of type <code>String<\/code>. A SecureString parameter is any sensitive data that needs to be stored and referenced in a secure manner. If you have data that you don&rsquo;t want users to alter or reference in plain text, such as passwords or license keys, create those parameters using the SecureString datatype.<\/p><\/div>\n\n<p>Although I used the normal <code>String<\/code> type you can do better for the above mentioned reasons.<\/p>\n<h2 id=\"quick-check\" class=\"group\">\n Quick check<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#quick-check\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>To quickly check if you can reach your site, navigate to your cluster, and check the task that is currently running. There you will find your public IP address that directly points to port <code>80<\/code> of your app. When you enter that page you should be welcomed with the Laravel landing page from our fresh install.<\/p>\n<h3 id=\"add-an-elastic-load-balancer-elb\" class=\"group\">\n Add an Elastic Load Balancer (ELB)<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#add-an-elastic-load-balancer-elb\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p><em>Note<\/em>: We won&rsquo;t handle neither HTTPS nor Amazon Route 53 here.<\/p>\n<p>After adding the load balancer you can point your domain to CNAME of ELB. The ELB is going to direct all traffic to port <code>80<\/code> of our application where our nginx is listening. A load balancer can be created in the EC2 service. On the left select Load balancers and hit the button <em>Create Load Balancer<\/em>. Next select Application Load Balancer.<\/p>\n<h4 id=\"create-an-elb-step-1\" class=\"group\">\n Create an ELB: Step 1<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-an-elb-step-1\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p>In the first step make sure you<\/p>\n<ul>\n<li>have a listener for HTTP on port <code>80<\/code><\/li>\n<li>have the correct Virtual Private Cloud (VPC) selected.<\/li>\n<\/ul>\n<p>Additionally add the availability zones for your different subnets. Next would be step 2 which we are going to skip, as it is only about HTTPS.<\/p>\n<h4 id=\"create-an-elb-step-3-security-group\" class=\"group\">\n Create an ELB: Step 3 (Security group)<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-an-elb-step-3-security-group\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p>We create a new security group <code>laravelapp-sg<\/code> with only one rule that allows traffic coming from <code>Anywhere<\/code> of type <code>HTTP<\/code> to reach our instance via <code>TCP<\/code> on port <code>80<\/code>.<\/p>\n<table class=\"data-table\">\n <thead>\n <tr>\n <th>Type<\/th>\n <th>Protocol<\/th>\n <th>Port range<\/th>\n <th>Source<\/th>\n <\/tr>\n <\/thead>\n <tbody>\n <tr>\n <td>HTTP<\/td>\n <td>TCP<\/td>\n <td>80<\/td>\n <td>Anywhere<\/td>\n <\/tr>\n <\/tbody>\n<\/table>\n<h4 id=\"create-an-elb-step-4-target-group\" class=\"group\">\n Create an ELB: Step 4 (Target group)<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-an-elb-step-4-target-group\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p>Here we create a new target group <code>laravelapp-tg<\/code> with a target type <code>IP<\/code>. Our load balancer routes requests to the targets in this target group using the protocol and port that you specify.<\/p>\n<h4 id=\"create-an-elb-step-56\" class=\"group\">\n Create an ELB: Step 5+6<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#create-an-elb-step-56\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h4><p>The <em>Register Targets<\/em> step can be skipped and on step 6 you please check all the settings again. If everything looks good, you can save the configuration of the ELB.<\/p>\n<h2 id=\"conclusions\" class=\"group\">\n Conclusions<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#conclusions\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h2><p>Now when pushing changes to your Github repository the deploy workflow start, build the images, pushed them to the ECR Docker registry, creates the task that is picked up by the service and creates your application.<\/p>\n<h3 id=\"costs\" class=\"group\">\n Costs<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#costs\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>So, what&rsquo;s the price you have to pay for this setup? As I mentioned earlier, this is hard to say, as it depends on usage, dimensioning and the region. <a href=\"https:\/\/www.concurrencylabs.com\/blog\/choose-your-aws-region-wisely\/\">Here<\/a> is a nice overview, how the different AWS regions varies by costs.<\/p>\n<p>To make it short, the top 5:<\/p>\n<ul>\n<li>N. Virginia<\/li>\n<li>Ohio<\/li>\n<li>Oregon<\/li>\n<li>Mumbai<\/li>\n<li>Stockholm<\/li>\n<\/ul>\n<p>Use the <a href=\"https:\/\/calculator.aws\/\">AWS pricing calculator<\/a> to get a proper pricing. If you&rsquo;re in a first phase of your project you are probably eligible for the AWS <a href=\"http:\/\/aws.amazon.com\/free\">Free Usage Tier<\/a>. This will surely give you a lot of space to play around and test. Here is a list of the usage for the service I created and played around with for this post.<\/p>\n<p>\n\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n <figure>\n <a href=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/free_tiers.png\" target=\"_blank\">\n <picture>\n <source media=\"(max-width: 376px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/free_tiers_hu_fda4168844aa64fc.webp\">\n <source media=\"(max-width: 992px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/free_tiers_hu_f5849e91d2a886e0.webp\">\n <source media=\"(max-width: 1400px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/free_tiers_hu_c8fb589b3b152902.webp\">\n <source media=\"(min-width: 1600px)\" srcset=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/free_tiers_hu_48ead6198a42e5c.webp\">\n\n <img class=\"img\" alt=\"Free tiers\" title=\"Free tiers\"\n src=\"https:\/\/www.codedge.de\/posts\/run-php-on-aws-fargate\/free_tiers.png\" loading=\"lazy\" decoding=\"async\"\n height=\"1268\" width=\"2786\">\n <\/picture>\n <\/a>\n \n <figcaption>\n Free tiers\n <\/figcaption>\n \n <\/figure>\n \n\n\n<\/p>\n<p>As you can see the most important for now is the space for our Docker registry on ECR. So to keep our images small is basically saving us money.<\/p>\n<h3 id=\"additional-ideas\" class=\"group\">\n Additional ideas<a class=\"hidden group-hover:inline-block ml-2 -rotate-45 relative no-underline opacity-65\" href=\"#additional-ideas\"><svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"0.8em\" height=\"0.8em\" viewBox=\"0 0 2048 2048\"><path fill=\"currentColor\" d=\"M1536 768v128q76 0 145 17t123 56t84 99t32 148q0 66-25 124t-69 101t-102 69t-124 26h-512q-66 0-124-25t-101-69t-69-102t-26-124q0-87 31-147t85-99t122-56t146-18V768h-64q-93 0-174 35t-142 96t-96 142t-36 175q0 93 35 174t96 142t142 96t175 36h512q93 0 174-35t142-96t96-142t36-175q0-93-35-174t-96-142t-142-96t-175-36zm-640 512v-128q76 0 145-17t123-56t84-99t32-148q0-66-25-124t-69-101t-102-69t-124-26H448q-66 0-124 25t-101 69t-69 102t-26 124q0 87 31 147t85 99t122 56t146 18v128h-64q-93 0-174-35t-142-96t-96-142T0 832q0-93 35-174t96-142t142-96t175-36h512q93 0 174 35t142 96t96 142t36 175q0 93-35 174t-96 142t-142 96t-175 36z\"\/><\/svg><\/a>\n<\/h3><p>Just a quick rundown on improvements further ideas to be done. Provide HTTPS access, refine the groups, policies in IAM to tighten access and strengthen security, see the <a href=\"https:\/\/github.com\/aws\/aws-sdk-php-laravel\">AWS SDK for Laravel<\/a> to make handling for AWS in Laravel easier<\/p>\n<p>&hellip; and probably many many more things :-)<\/p>"}]}}