<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Beppe</title>
    <description>The latest articles on DEV Community by Beppe (@beppe90).</description>
    <link>https://dev.to/beppe90</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F429500%2F916b7ec4-37ec-4b08-b9d3-443b43a30035.jpg</url>
      <title>DEV Community: Beppe</title>
      <link>https://dev.to/beppe90</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/beppe90"/>
    <language>en</language>
    <item>
      <title>The Home Server Journey - 6: Your New Blogging Career</title>
      <dc:creator>Beppe</dc:creator>
      <pubDate>Tue, 08 Oct 2024 22:48:36 +0000</pubDate>
      <link>https://dev.to/beppe90/the-home-server-journey-6-your-new-blogging-career-4jp6</link>
      <guid>https://dev.to/beppe90/the-home-server-journey-6-your-new-blogging-career-4jp6</guid>
      <description>&lt;p&gt;Hello, folks!&lt;/p&gt;

&lt;p&gt;One thing that bothers me when writing about self-hosting, to have greater control of my data, is that I don't apply those principles to the articles themselves. I mean, there is no immediate risk of &lt;strong&gt;Minds&lt;/strong&gt; or &lt;strong&gt;Dev.to&lt;/strong&gt; taking down my publications, but the mere fact that they &lt;em&gt;could&lt;/em&gt; do that leaves me concerned &lt;/p&gt;

&lt;h3&gt;
  
  
  The Right Tool for the Job
&lt;/h3&gt;

&lt;p&gt;First I've looked at the tools I was already familiar with. I have some &lt;a href="https://beppeinfo.github.io/" rel="noopener noreferrer"&gt;old blog&lt;/a&gt; where I've posted updates during my &lt;a href="https://summerofcode.withgoogle.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Google Summer of Code&lt;/strong&gt;&lt;/a&gt; projects. It uses &lt;a href="https://jekyllrb.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Jekyll&lt;/strong&gt;&lt;/a&gt; to generate &lt;a href="https://en.wikipedia.org/wiki/Static_web_page" rel="noopener noreferrer"&gt;static files&lt;/a&gt;, automatically published by &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub Pages&lt;/strong&gt;&lt;/a&gt;. It works very well when you have the website tied to a &lt;a href="https://www.atlassian.com/git/tutorials/what-is-version-control" rel="noopener noreferrer"&gt;version-controlled&lt;/a&gt; repository, but it's cumbersome when you need to rebuild container images or replace files in a remote volume even for small changes&lt;/p&gt;

&lt;p&gt;When looking for something more dynamic, I initially though about using &lt;a href="https://joinplu.me/" rel="noopener noreferrer"&gt;&lt;strong&gt;Plume&lt;/strong&gt;&lt;/a&gt;, since it's easy to integrate with some applications I plan to deploy later, but unfortunately it's not well maintained anymore. As &lt;strong&gt;Ghost&lt;/strong&gt; or &lt;strong&gt;Wordpress&lt;/strong&gt; seem overkill, I ended up opting for the conveniences of &lt;a href="https://writefreely.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;WriteFreely&lt;/strong&gt;&lt;/a&gt;: it lets me create and edit posts in-place, with &lt;a href="https://www.markdownguide.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Markdown&lt;/strong&gt;&lt;/a&gt; support and no need to upload new files. However, that comes with a cost: it requires a &lt;a href="https://www.mysql.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;MySQL&lt;/strong&gt;&lt;/a&gt;[-compatible] database &lt;/p&gt;

&lt;h3&gt;
  
  
  Contrarian Vibes
&lt;/h3&gt;

&lt;p&gt;It seems easy enough to just deploy a &lt;strong&gt;MySQL&lt;/strong&gt; container and use it, right? Well... It seems that &lt;a href="https://mariadb.org/en/#:~:text=MariaDB%20Server-,History,after%20his%20second%20daughter%2C%20Maria." rel="noopener noreferrer"&gt;there are some concerns about its licensing and development direction ever since the brand has been bought by &lt;strong&gt;Oracle&lt;/strong&gt;&lt;/a&gt; (remember &lt;a href="https://en.wikipedia.org/wiki/OpenOffice.org" rel="noopener noreferrer"&gt;&lt;strong&gt;OpenOffice&lt;/strong&gt;&lt;/a&gt;?). That was the motivation for the &lt;a href="https://mariadb.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;MariaDB&lt;/strong&gt;&lt;/a&gt; fork, distributed under the &lt;strong&gt;GPLv2&lt;/strong&gt; license, which nowadays is not even a 100% drop-in replacement for &lt;strong&gt;MySQL&lt;/strong&gt;, but still works for our case &lt;/p&gt;

&lt;p&gt;Reputation-related shenanigans aside, one great advantage of picking &lt;strong&gt;MariaDB&lt;/strong&gt; is the ability to use a &lt;a href="https://mariadb.com/kb/en/galera-cluster/" rel="noopener noreferrer"&gt;&lt;strong&gt;Galera&lt;/strong&gt; Cluster&lt;/a&gt;. Similarly to &lt;a href="https://dev.to/beppe90/the-home-server-journey-5b-a-bridge-too-far-j0l"&gt;what we did for &lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/a&gt;, I wish to be able to scale it properly, and &lt;strong&gt;Galera&lt;/strong&gt;'s replication works in an even more interesting manner, with multiple primary (read-write) instances and no need for a separate proxy!:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2221vgcikcr05hmnj4v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2221vgcikcr05hmnj4v.png" alt="MariaDB-Galera topology" width="800" height="422"&gt;&lt;/a&gt;&lt;br&gt;
(Man, I wish &lt;strong&gt;PostgreSQL&lt;/strong&gt; had something similar...)&lt;/p&gt;

&lt;p&gt;Of course that requires a more complex setup for the database server itself, but thanks to &lt;strong&gt;Bitnami&lt;/strong&gt;'s &lt;strong&gt;mariadb-galera&lt;/strong&gt; &lt;a href="https://github.com/bitnami/containers/tree/main/bitnami/mariadb-galera" rel="noopener noreferrer"&gt;&lt;strong&gt;Docker&lt;/strong&gt; image&lt;/a&gt; and &lt;a href="https://github.com/bitnami/charts/tree/main/bitnami/mariadb-galera" rel="noopener noreferrer"&gt;&lt;strong&gt;Helm&lt;/strong&gt; chart&lt;/a&gt;, I've managed to get to something rather manageable for our purposes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;BITNAMI_DEBUG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;                 &lt;span class="c1"&gt;# Set to "true" for more debug information&lt;/span&gt;
  &lt;span class="na"&gt;MARIADB_GALERA_CLUSTER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;galera&lt;/span&gt;
  &lt;span class="c1"&gt;# All pods being synchronized (has to reflect the number of replicas)&lt;/span&gt;
  &lt;span class="na"&gt;MARIADB_GALERA_CLUSTER_ADDRESS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcomm://mariadb-state-0.mariadb-replication-service.default.svc.cluster.local,mariadb-state-1.mariadb-replication-service.default.svc.cluster.local&lt;/span&gt;
  &lt;span class="na"&gt;MARIADB_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;                      &lt;span class="c1"&gt;# Default database&lt;/span&gt;
  &lt;span class="na"&gt;MARIADB_GALERA_MARIABACKUP_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backup&lt;/span&gt;     &lt;span class="c1"&gt;# Replication user&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Source: mariadb-galera/templates/secrets.yaml&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-secret&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;MARIADB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bWFyaWFkYg==&lt;/span&gt;             &lt;span class="c1"&gt;# Administrator password&lt;/span&gt;
  &lt;span class="na"&gt;MARIADB_GALERA_MARIABACKUP_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YmFja3Vw&lt;/span&gt;   &lt;span class="c1"&gt;# Replication user password&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-cnf-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                             &lt;span class="c1"&gt;# Database server configuration&lt;/span&gt;
  &lt;span class="na"&gt;my.cnf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;[client]&lt;/span&gt;
    &lt;span class="s"&gt;port=3306&lt;/span&gt;
    &lt;span class="s"&gt;socket=/opt/bitnami/mariadb/tmp/mysql.sock&lt;/span&gt;
    &lt;span class="s"&gt;plugin_dir=/opt/bitnami/mariadb/plugin&lt;/span&gt;

    &lt;span class="s"&gt;[mysqld]&lt;/span&gt;
    &lt;span class="s"&gt;explicit_defaults_for_timestamp&lt;/span&gt;
    &lt;span class="s"&gt;default_storage_engine=InnoDB&lt;/span&gt;
    &lt;span class="s"&gt;basedir=/opt/bitnami/mariadb&lt;/span&gt;
    &lt;span class="s"&gt;datadir=/bitnami/mariadb/data&lt;/span&gt;
    &lt;span class="s"&gt;plugin_dir=/opt/bitnami/mariadb/plugin&lt;/span&gt;
    &lt;span class="s"&gt;tmpdir=/opt/bitnami/mariadb/tmp&lt;/span&gt;
    &lt;span class="s"&gt;socket=/opt/bitnami/mariadb/tmp/mysql.sock&lt;/span&gt;
    &lt;span class="s"&gt;pid_file=/opt/bitnami/mariadb/tmp/mysqld.pid&lt;/span&gt;
    &lt;span class="s"&gt;bind_address=0.0.0.0&lt;/span&gt;

    &lt;span class="s"&gt;## Character set&lt;/span&gt;
    &lt;span class="s"&gt;##&lt;/span&gt;
    &lt;span class="s"&gt;collation_server=utf8_unicode_ci&lt;/span&gt;
    &lt;span class="s"&gt;init_connect='SET NAMES utf8'&lt;/span&gt;
    &lt;span class="s"&gt;character_set_server=utf8&lt;/span&gt;

    &lt;span class="s"&gt;## MyISAM&lt;/span&gt;
    &lt;span class="s"&gt;##&lt;/span&gt;
    &lt;span class="s"&gt;key_buffer_size=32M&lt;/span&gt;
    &lt;span class="s"&gt;myisam_recover_options=FORCE,BACKUP&lt;/span&gt;

    &lt;span class="s"&gt;## Safety&lt;/span&gt;
    &lt;span class="s"&gt;##&lt;/span&gt;
    &lt;span class="s"&gt;skip_host_cache&lt;/span&gt;
    &lt;span class="s"&gt;skip_name_resolve&lt;/span&gt;
    &lt;span class="s"&gt;max_allowed_packet=16M&lt;/span&gt;
    &lt;span class="s"&gt;max_connect_errors=1000000&lt;/span&gt;
    &lt;span class="s"&gt;sql_mode=STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ONLY_FULL_GROUP_BY&lt;/span&gt;
    &lt;span class="s"&gt;sysdate_is_now=1&lt;/span&gt;

    &lt;span class="s"&gt;## Binary Logging&lt;/span&gt;
    &lt;span class="s"&gt;##&lt;/span&gt;
    &lt;span class="s"&gt;log_bin=mysql-bin&lt;/span&gt;
    &lt;span class="s"&gt;expire_logs_days=14&lt;/span&gt;
    &lt;span class="s"&gt;# Disabling for performance per http://severalnines.com/blog/9-tips-going-production-galera-cluster-mysql&lt;/span&gt;
    &lt;span class="s"&gt;sync_binlog=0&lt;/span&gt;
    &lt;span class="s"&gt;# Required for Galera&lt;/span&gt;
    &lt;span class="s"&gt;binlog_format=row&lt;/span&gt;

    &lt;span class="s"&gt;## Caches and Limits&lt;/span&gt;
    &lt;span class="s"&gt;##&lt;/span&gt;
    &lt;span class="s"&gt;tmp_table_size=32M&lt;/span&gt;
    &lt;span class="s"&gt;max_heap_table_size=32M&lt;/span&gt;
    &lt;span class="s"&gt;# Re-enabling as now works with Maria 10.1.2&lt;/span&gt;
    &lt;span class="s"&gt;query_cache_type=1&lt;/span&gt;
    &lt;span class="s"&gt;query_cache_limit=4M&lt;/span&gt;
    &lt;span class="s"&gt;query_cache_size=256M&lt;/span&gt;
    &lt;span class="s"&gt;max_connections=500&lt;/span&gt;
    &lt;span class="s"&gt;thread_cache_size=50&lt;/span&gt;
    &lt;span class="s"&gt;open_files_limit=65535&lt;/span&gt;
    &lt;span class="s"&gt;table_definition_cache=4096&lt;/span&gt;
    &lt;span class="s"&gt;table_open_cache=4096&lt;/span&gt;

    &lt;span class="s"&gt;## InnoDB&lt;/span&gt;
    &lt;span class="s"&gt;##&lt;/span&gt;
    &lt;span class="s"&gt;innodb=FORCE&lt;/span&gt;
    &lt;span class="s"&gt;innodb_strict_mode=1&lt;/span&gt;
    &lt;span class="s"&gt;# Mandatory per https://github.com/codership/documentation/issues/25&lt;/span&gt;
    &lt;span class="s"&gt;innodb_autoinc_lock_mode=2&lt;/span&gt;
    &lt;span class="s"&gt;# Per https://www.percona.com/blog/2006/08/04/innodb-double-write/&lt;/span&gt;
    &lt;span class="s"&gt;innodb_doublewrite=1&lt;/span&gt;
    &lt;span class="s"&gt;innodb_flush_method=O_DIRECT&lt;/span&gt;
    &lt;span class="s"&gt;innodb_log_files_in_group=2&lt;/span&gt;
    &lt;span class="s"&gt;innodb_log_file_size=128M&lt;/span&gt;
    &lt;span class="s"&gt;innodb_flush_log_at_trx_commit=1&lt;/span&gt;
    &lt;span class="s"&gt;innodb_file_per_table=1&lt;/span&gt;
    &lt;span class="s"&gt;# 80% Memory is default reco.&lt;/span&gt;
    &lt;span class="s"&gt;# Need to re-evaluate when DB size grows&lt;/span&gt;
    &lt;span class="s"&gt;innodb_buffer_pool_size=2G&lt;/span&gt;
    &lt;span class="s"&gt;innodb_file_format=Barracuda&lt;/span&gt;

    &lt;span class="s"&gt;[galera]&lt;/span&gt;
    &lt;span class="s"&gt;wsrep_on=ON&lt;/span&gt;
    &lt;span class="s"&gt;wsrep_provider=/opt/bitnami/mariadb/lib/libgalera_smm.so&lt;/span&gt;
    &lt;span class="s"&gt;wsrep_sst_method=mariabackup&lt;/span&gt;
    &lt;span class="s"&gt;wsrep_slave_threads=4&lt;/span&gt;
    &lt;span class="s"&gt;wsrep_cluster_address=gcomm://&lt;/span&gt;
    &lt;span class="s"&gt;wsrep_cluster_name=galera&lt;/span&gt;
    &lt;span class="s"&gt;wsrep_sst_auth="root:"&lt;/span&gt;
    &lt;span class="s"&gt;# Enabled for performance per https://mariadb.com/kb/en/innodb-system-variables/#innodb_flush_log_at_trx_commit&lt;/span&gt;
    &lt;span class="s"&gt;innodb_flush_log_at_trx_commit=2&lt;/span&gt;
    &lt;span class="s"&gt;# MYISAM REPLICATION SUPPORT #&lt;/span&gt;
    &lt;span class="s"&gt;wsrep_mode=REPLICATE_MYISAM&lt;/span&gt;

    &lt;span class="s"&gt;[mariadb]&lt;/span&gt;
    &lt;span class="s"&gt;plugin_load_add=auth_pam&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-state&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-replication-service&lt;/span&gt;       &lt;span class="c1"&gt;# Use the internal/headless service name&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;              &lt;span class="c1"&gt;# Container is not run as root&lt;/span&gt;
        &lt;span class="na"&gt;fsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
        &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
        &lt;span class="na"&gt;runAsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/bitnami/mariadb-galera:11.5.2&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;   
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-ec&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
                &lt;span class="s"&gt;exec /opt/bitnami/scripts/mariadb-galera/entrypoint.sh /opt/bitnami/scripts/mariadb-galera/run.sh&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdb-mysql-port&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3306&lt;/span&gt;                &lt;span class="c1"&gt;# External access port (MySQL's default)&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdb-galera-port&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4567&lt;/span&gt;                &lt;span class="c1"&gt;# Internal process port&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdb-ist-port&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4568&lt;/span&gt;                &lt;span class="c1"&gt;# Internal process port&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdb-sst-port&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4444&lt;/span&gt;                &lt;span class="c1"&gt;# Internal process port&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-config&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MARIADB_ROOT_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MARIADB_ROOT_PASSWORD&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MARIADB_GALERA_MARIABACKUP_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MARIADB_GALERA_MARIABACKUP_PASSWORD&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;previous-boot&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/mariadb/.bootstrap&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-data&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bitnami/mariadb&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-cnf&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bitnami/conf/my.cnf&lt;/span&gt;               &lt;span class="c1"&gt;# Overwrite any present configuration&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my.cnf&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/tmp&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmp-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/mariadb/conf&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-conf-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/mariadb/tmp&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-tmp-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/mariadb/logs&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-logs-dir&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;previous-boot&lt;/span&gt;
          &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                   &lt;span class="c1"&gt;# Use a fake directory for mounting unused but required paths&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-cnf&lt;/span&gt;
          &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-cnf-config&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
          &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                   &lt;span class="c1"&gt;# Use a fake directory for mounting unused but required paths&lt;/span&gt;
  &lt;span class="na"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                  &lt;span class="c1"&gt;# Description of volume claim created for each replica&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-data&lt;/span&gt;
      &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-small&lt;/span&gt;
        &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8Gi&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Headless service for internal replication/backup processes&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-replication-service&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;clusterIP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-galera-service&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4567&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdb-galera-port&lt;/span&gt;
      &lt;span class="na"&gt;appProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-ist-service&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4568&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdb-ist-port&lt;/span&gt;
      &lt;span class="na"&gt;appProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-sst-service&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4444&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdb-sst-port&lt;/span&gt;
      &lt;span class="na"&gt;appProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
  &lt;span class="na"&gt;publishNotReadyAddresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Exposed service for external access&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;                        &lt;span class="c1"&gt;# Let it be accessible inside the local network&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3306&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mdb-mysql-port&lt;/span&gt;
      &lt;span class="na"&gt;appProtocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Incredibly, it works. My deployment has been running without issue for some time now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get all &lt;span class="nt"&gt;-n&lt;/span&gt; choppa &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mariadb                                                                                                                                                
NAME                  READY   STATUS    RESTARTS       AGE
pod/mariadb-state-0   1/1     Running   2 &lt;span class="o"&gt;(&lt;/span&gt;3d1h ago&lt;span class="o"&gt;)&lt;/span&gt;   5d3h
pod/mariadb-state-1   1/1     Running   2 &lt;span class="o"&gt;(&lt;/span&gt;3d1h ago&lt;span class="o"&gt;)&lt;/span&gt;   5d3h

NAME                                  TYPE           CLUSTER-IP     EXTERNAL-IP                 PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;                      AGE
service/mariadb-replication-service   ClusterIP      None           &amp;lt;none&amp;gt;                      4567/TCP,4568/TCP,4444/TCP   5d3h
service/mariadb-service               LoadBalancer   10.43.40.243   192.168.3.10,192.168.3.12   3306:31594/TCP               5d3h

NAME                             READY   AGE
statefulset.apps/mariadb-state   2/2     5d3h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The 2 restarts were due to a power outage that exceeded the autonomy of my no-break's battery)&lt;/p&gt;

&lt;h3&gt;
  
  
  Solving one Problem to Reveal Another
&lt;/h3&gt;

&lt;p&gt;I just started typing my first self-hosted blog post to realize something was missing: images. On &lt;strong&gt;Jekyll&lt;/strong&gt; I had a folder for that, but on &lt;strong&gt;Minds&lt;/strong&gt; and &lt;strong&gt;Dev.to&lt;/strong&gt; they are hosted somewhere else, e.g. &lt;code&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v2221vgcikcr05hmnj4v.png&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If complete self-hosting is a must, I now need some file server capable of generating shareable links, to be used in my &lt;strong&gt;Markdown&lt;/strong&gt; image components. In summary, &lt;a href="https://syncthing.net/" rel="noopener noreferrer"&gt;&lt;strong&gt;Syncthing&lt;/strong&gt;&lt;/a&gt; is great for &lt;strong&gt;Dropbox&lt;/strong&gt;-style backups, but can't share links, &lt;a href="https://nextcloud.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;NextCloud&lt;/strong&gt;&lt;/a&gt; is too resource-heavy and &lt;a href="https://www.seafile.com/en/home/" rel="noopener noreferrer"&gt;&lt;strong&gt;Seafile&lt;/strong&gt;&lt;/a&gt; is interesting but apparently &lt;a href="https://www.reddit.com/r/selfhosted/comments/151clya/comment/jsanslz/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button" rel="noopener noreferrer"&gt;has proprietary encryption&lt;/a&gt;, which left me with the lightweight &lt;a href="https://filebrowser.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Filebrowser&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't expect or intend my file server to ever deal with a huge number of requests, so I've ran it as a simple deployment with a single pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;       &lt;span class="c1"&gt;# Storage requirements component&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-pv-claim&lt;/span&gt;          
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-big&lt;/span&gt;       &lt;span class="c1"&gt;# The used storage class (1TB drive)&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
    &lt;span class="c1"&gt;#- ReadWriteMany               # For concurrent access (In case I try to use more replicas)&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;200Gi&lt;/span&gt;               &lt;span class="c1"&gt;# Asking for a ~50 Gigabytes volume&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                       &lt;span class="c1"&gt;# Application settings file&lt;/span&gt;
  &lt;span class="na"&gt;.filebrowser.json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;{&lt;/span&gt;
      &lt;span class="s"&gt;"port": 80,&lt;/span&gt;
      &lt;span class="s"&gt;"baseURL": "",&lt;/span&gt;
      &lt;span class="s"&gt;"address": "",&lt;/span&gt;
      &lt;span class="s"&gt;"log": "stdout",&lt;/span&gt;
      &lt;span class="s"&gt;"database": "/srv/filebrowser.db",&lt;/span&gt;
      &lt;span class="s"&gt;"root": "/srv"&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-deploy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;              
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recreate&lt;/span&gt;                  &lt;span class="c1"&gt;# Wait for the old container to be terminated before creating a new one&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# Run this initial container to make sure at least an empty &lt;/span&gt;
      &lt;span class="c1"&gt;# database file exists prior to the main container starting,&lt;/span&gt;
      &lt;span class="c1"&gt;# as a workaround for a know bug (https://filebrowser.org/installation#docker)&lt;/span&gt;
      &lt;span class="na"&gt;initContainers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create-database&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/touch"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/srv/filebrowser.db"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-data&lt;/span&gt;
            &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser/filebrowser:latest&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file-port&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;80&lt;/span&gt;                 
              &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-readonly&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/.filebrowser.json&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.filebrowser.json&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-data&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/srv&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-readonly&lt;/span&gt;
          &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-config&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-data&lt;/span&gt;                        &lt;span class="c1"&gt;# Label the volume for this deployment&lt;/span&gt;
          &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-pv-claim&lt;/span&gt;           &lt;span class="c1"&gt;# Reference volumen create by the claim&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&lt;/span&gt;               &lt;span class="c1"&gt;# Expose the service outside the cluster with an specific port&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8080&lt;/span&gt;                                 
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file-port&lt;/span&gt;
      &lt;span class="na"&gt;nodePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(That's what I did here, make it makes the &lt;code&gt;filebrowser.db&lt;/code&gt; file end up visible inside the root folder. It's probably a good idea to use &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/#using-subpath" rel="noopener noreferrer"&gt;&lt;strong&gt;subpaths&lt;/strong&gt;&lt;/a&gt; and mount them separately e.g. &lt;code&gt;srv/filebrowser.db&lt;/code&gt; and &lt;code&gt;srv/data&lt;/code&gt; for root)&lt;/p&gt;

&lt;p&gt;We can't upload or access the files from the Internet yet, but using &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport" rel="noopener noreferrer"&gt;&lt;strong&gt;NodePort&lt;/strong&gt;&lt;/a&gt; an external port in the range &lt;code&gt;30000-32767&lt;/code&gt; can be used to reach it locally. Use the default username &lt;code&gt;admin&lt;/code&gt; and password &lt;code&gt;admin&lt;/code&gt; to login and then change it in the settings:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bqfugum7nwenvmgbzc7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3bqfugum7nwenvmgbzc7.png" alt="Filebrowser screen" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on each file you wish to share and the option to generate links will appear on the top. In &lt;strong&gt;Markdown&lt;/strong&gt; syntax, shared images may be annexed with the statement &lt;code&gt;![Image description](https://&amp;lt;your host&amp;gt;/api/public/dl/&amp;lt;share hash&amp;gt;?inline=true)&lt;/code&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  One Step Forward. Two Steps Back
&lt;/h3&gt;

&lt;p&gt;All set to deploy &lt;strong&gt;WriteFreely&lt;/strong&gt;, right? As you might guess, no&lt;/p&gt;

&lt;p&gt;The application doesn't have an official &lt;strong&gt;Docker&lt;/strong&gt; image, and the custom ones available are either too old or not available for the &lt;strong&gt;ARM64&lt;/strong&gt; architecture. &lt;a href="https://github.com/karlprieb/writefreely-docker" rel="noopener noreferrer"&gt;The repository provided by &lt;strong&gt;karlprieb&lt;/strong&gt;&lt;/a&gt; is a good base to build your own, but it lead to crashes here when compiling the application itself. In the end, I found it easier to create one taking advantage of &lt;strong&gt;Alpine Linux&lt;/strong&gt;'s packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dockerfile
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine:3.20&lt;/span&gt;
&lt;span class="k"&gt;LABEL&lt;/span&gt;&lt;span class="s"&gt; org.opencontainers.image.description="Simple WriteFreely image based on https://git.madhouse-project.org/algernon/writefreely-docker"&lt;/span&gt;
&lt;span class="c"&gt;# Install the writefreely package&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; writefreely
&lt;span class="c"&gt;# Installation creates the writefreely user, so let's use it&lt;/span&gt;
&lt;span class="c"&gt;# to run the application&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /opt/writefreely  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;chown &lt;/span&gt;writefreely &lt;span class="nt"&gt;-R&lt;/span&gt; /opt/writefreely
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=writefreely:writefreely ./run.sh /opt/writefreely/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /opt/writefreely/run.sh
&lt;span class="c"&gt;# Base directory and exposed container port&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/writefreely/&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="c"&gt;# Set the default container user and group&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; writefreely:writefreely&lt;/span&gt;
&lt;span class="c"&gt;# Start script&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["/opt/writefreely/run.sh"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Entrypoint script (run.sh)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! /bin/sh&lt;/span&gt;

writefreely &lt;span class="nt"&gt;-c&lt;/span&gt; /data/config.ini &lt;span class="nt"&gt;--init-db&lt;/span&gt;
writefreely &lt;span class="nt"&gt;-c&lt;/span&gt; /data/config.ini &lt;span class="nt"&gt;--gen-keys&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WRITEFREELY_ADMIN_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WRITEFREELY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;writefreely &lt;span class="nt"&gt;-c&lt;/span&gt; /data/config.ini &lt;span class="nt"&gt;--create-admin&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WRITEFREELY_ADMIN_USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WRITEFREELY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;writefreely &lt;span class="nt"&gt;-c&lt;/span&gt; /data/config.ini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I've published the image to &lt;code&gt;ancapepe/writefreely:latest&lt;/code&gt; &lt;a href="https://hub.docker.com/repository/docker/ancapepe/writefreely/general" rel="noopener noreferrer"&gt;on &lt;strong&gt;DockerHub&lt;/strong&gt;&lt;/a&gt;, so use it if you wish and have no desire for alternative themes or other custom stuff. One more thing to do before running our blog is to prepare the database to receive its content, so log into the &lt;strong&gt;MariaDB&lt;/strong&gt; server on port &lt;code&gt;3306&lt;/code&gt; using you root user and execute those commands, replacing username and password to your liking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;writefreely&lt;/span&gt; &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;latin1&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;latin1_swedish_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'blog'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'my_password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;writefreely&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'blog'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now apply a &lt;strong&gt;K8s&lt;/strong&gt; manifest matching previous configurations and adjusting new ones to your liking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;WRITEFREELY_ADMIN_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_user&lt;/span&gt;
  &lt;span class="na"&gt;config.ini&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;[server]&lt;/span&gt;
    &lt;span class="s"&gt;hidden_host          =&lt;/span&gt;
    &lt;span class="s"&gt;port                 = 8080&lt;/span&gt;
    &lt;span class="s"&gt;bind                 = 0.0.0.0&lt;/span&gt;
    &lt;span class="s"&gt;tls_cert_path        =&lt;/span&gt;
    &lt;span class="s"&gt;tls_key_path         =&lt;/span&gt;
    &lt;span class="s"&gt;templates_parent_dir = /usr/share/writefreely&lt;/span&gt;
    &lt;span class="s"&gt;static_parent_dir    = /usr/share/writefreely&lt;/span&gt;
    &lt;span class="s"&gt;pages_parent_dir     = /usr/share/writefreely&lt;/span&gt;
    &lt;span class="s"&gt;keys_parent_dir      = &lt;/span&gt;

    &lt;span class="s"&gt;[database]&lt;/span&gt;
    &lt;span class="s"&gt;type     = mysql&lt;/span&gt;
    &lt;span class="s"&gt;username = blog&lt;/span&gt;
    &lt;span class="s"&gt;password = my_password&lt;/span&gt;
    &lt;span class="s"&gt;database = writefreely&lt;/span&gt;
    &lt;span class="s"&gt;host     = mariadb-service&lt;/span&gt;
    &lt;span class="s"&gt;port     = 3306&lt;/span&gt;

    &lt;span class="s"&gt;[app]&lt;/span&gt;
    &lt;span class="s"&gt;site_name         = Get To The Choppa&lt;/span&gt;
    &lt;span class="s"&gt;site_description  = Notes on Conscious Self-Ownership&lt;/span&gt;
    &lt;span class="s"&gt;host              = https://blog.choppa.xyz&lt;/span&gt;
    &lt;span class="s"&gt;editor            = &lt;/span&gt;
    &lt;span class="s"&gt;theme             = write&lt;/span&gt;
    &lt;span class="s"&gt;disable_js        = false&lt;/span&gt;
    &lt;span class="s"&gt;webfonts          = true&lt;/span&gt;
    &lt;span class="s"&gt;landing           = /login&lt;/span&gt;
    &lt;span class="s"&gt;single_user       = true&lt;/span&gt;
    &lt;span class="s"&gt;open_registration = false&lt;/span&gt;
    &lt;span class="s"&gt;min_username_len  = 3&lt;/span&gt;
    &lt;span class="s"&gt;max_blogs         = 1&lt;/span&gt;
    &lt;span class="s"&gt;federation        = true&lt;/span&gt;
    &lt;span class="s"&gt;public_stats      = true&lt;/span&gt;
    &lt;span class="s"&gt;private           = false&lt;/span&gt;
    &lt;span class="s"&gt;local_timeline    = true&lt;/span&gt;
    &lt;span class="s"&gt;user_invites      = admin&lt;/span&gt;
&lt;span class="c1"&gt;# If you wish to change the shortcut icon for your blog without modifying the image itself, add here the configmap entry generated by running `kubectl create configmap favicon-config --from-file=&amp;lt;your .ico image path&amp;gt;`&lt;/span&gt;
&lt;span class="na"&gt;binaryData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;favicon.ico&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;binary dump here&amp;gt;&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-secret&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;WRITEFREELY_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bXlfcGFzc3dvcmQ=&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-deploy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ancapepe/writefreely:latest&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog-port&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WRITEFREELY_ADMIN_USER&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;configMapKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-config&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WRITEFREELY_ADMIN_USER&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WRITEFREELY_ADMIN_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WRITEFREELY_ADMIN_PASSWORD&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/config.ini&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config.ini&lt;/span&gt;
            &lt;span class="c1"&gt;# Use this if you set the custom favicon.ico image above&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/share/writefreely/static/favicon.ico&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;favicon.ico&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-volume&lt;/span&gt;
        &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-config&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publishNotReadyAddresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog-port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(You may add your own &lt;code&gt;favicon.ico&lt;/code&gt; to the image itself if you're building it)&lt;/p&gt;

&lt;p&gt;Almost there. Now we just have to expose both our blog pages and file server to the Internet by adding the corresponding entries to our ingress component:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;                    
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;                                     &lt;span class="c1"&gt;# Component type&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;                                     &lt;span class="c1"&gt;# Component name&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;                               &lt;span class="c1"&gt;# You may add the default namespace for components as a paramenter&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cert-manager.io/cluster-issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;letsencrypt&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;loadBalancer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                      
  &lt;span class="na"&gt;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;                       &lt;span class="c1"&gt;# Type of controller being used&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;choppa.xyz&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;talk.choppa.xyz&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;blog.choppa.xyz&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;files.choppa.xyz&lt;/span&gt;
    &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certificate&lt;/span&gt;      
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                          &lt;span class="c1"&gt;# Routing rules&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa.xyz&lt;/span&gt;                              &lt;span class="c1"&gt;# Expected domain name of request, including subdomain&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                         &lt;span class="c1"&gt;# For HTTP or HTTPS requests&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                      &lt;span class="c1"&gt;# Behavior for different base paths&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;                                 &lt;span class="c1"&gt;# For all request paths&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-service&lt;/span&gt;               &lt;span class="c1"&gt;# Redirect to this service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;                      &lt;span class="c1"&gt;# Redirect to this internal service port&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/.well-known/matrix/&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ImplementationSpecific&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8448&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;talk.choppa.xyz&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8448&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test.choppa.xyz&lt;/span&gt;                         &lt;span class="c1"&gt;# Expected domain name of request, including subdomain&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                         &lt;span class="c1"&gt;# For HTTP or HTTPS requests&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                      &lt;span class="c1"&gt;# Behavior for different base paths&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;                                 &lt;span class="c1"&gt;# For all request paths&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-service&lt;/span&gt;                  &lt;span class="c1"&gt;# Redirect to this service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;                        &lt;span class="c1"&gt;# Redirect to this internal service port&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;blog.choppa.xyz&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;writefreely-service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;files.choppa.xyz&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;filebrowser-service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything went accordingly, you now have everything in place to log into your blog and start publishing. To get an idea of how your self-hosted articles will look like, pay a visit to &lt;a href="https://blog.choppa.xyz/the-home-server-journey-1" rel="noopener noreferrer"&gt;the first chapter of this series&lt;/a&gt; that I'm starting to publish on my server as well:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16o1at7nb73rim3ue2tm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16o1at7nb73rim3ue2tm.png" alt="Blog page" width="800" height="721"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for following along. See you next time&lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>kubernetes</category>
      <category>decentralization</category>
      <category>censorship</category>
    </item>
    <item>
      <title>The Home Server Journey - 5b: A Bridge Too Far?</title>
      <dc:creator>Beppe</dc:creator>
      <pubDate>Mon, 07 Oct 2024 10:13:49 +0000</pubDate>
      <link>https://dev.to/beppe90/the-home-server-journey-5b-a-bridge-too-far-j0l</link>
      <guid>https://dev.to/beppe90/the-home-server-journey-5b-a-bridge-too-far-j0l</guid>
      <description>&lt;p&gt;Hi all. This is a late addendum to &lt;a href="https://dev.to/ancapepe/the-home-server-journey-5-rebuilding-burned-bridges-6n7"&gt;my last post&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As I have found out, &lt;a href="https://www.imdb.com/title/tt0075784/" rel="noopener noreferrer"&gt;like the Allies had for those river crossings at Operation Market Garden&lt;/a&gt;, &lt;strong&gt;stateful sets&lt;/strong&gt; are not as trivial as they initially appear: most guides will just tell you what's their purpose and how to get them running, which leaves a false impression that synchronized data replication across pods happens automagically (sic)&lt;/p&gt;

&lt;p&gt;Well, it doesn't&lt;/p&gt;

&lt;h3&gt;
  
  
  Crossing that River. No Matter the Costs
&lt;/h3&gt;

&lt;p&gt;That special type of &lt;strong&gt;deployment&lt;/strong&gt; will only give you guarantees regarding the order of pods creation and deletion, their naming scheme and which &lt;strong&gt;persistent volume&lt;/strong&gt; they will be bound to. Anything else is on the application logic. You may even violate the principle of using only the first pod for writing and the other ones for reading&lt;/p&gt;

&lt;p&gt;When it comes to more niche applications like &lt;strong&gt;Conduit&lt;/strong&gt;, I will probably have to code my own replication solution at some point, but for more widely used software like &lt;strong&gt;PostgreSQL&lt;/strong&gt; there are solutions already available, thankfully&lt;/p&gt;

&lt;p&gt;I came across articles by &lt;a href="https://devopscube.com/deploy-postgresql-statefulset/" rel="noopener noreferrer"&gt;Bibin Wilson &amp;amp; Shishir Khandelwal&lt;/a&gt; and &lt;a href="https://weng-albert.medium.com/mastering-high-availability-postgresql-meets-kubernetes-929846f6cc88" rel="noopener noreferrer"&gt;Albert Weng&lt;/a&gt; (&lt;a href="https://weng-albert.medium.com/how-to-create-an-nfs-storageclass-en-fe962242f44e" rel="noopener noreferrer"&gt;we've seen him here before&lt;/a&gt;) detailing how to use a special variant of the database image to get replication working. Although a bit outdated, due to the &lt;a href="https://hub.docker.com/r/bitnami/postgresql-repmgr" rel="noopener noreferrer"&gt;&lt;strong&gt;Docker&lt;/strong&gt; registry used&lt;/a&gt; I'm pretty sure that's based on the &lt;a href="https://github.com/bitnami/charts/tree/main/bitnami/postgresql-ha" rel="noopener noreferrer"&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; High Availability &lt;strong&gt;Helm&lt;/strong&gt; chart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I don't plan on covering &lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;&lt;strong&gt;Helm&lt;/strong&gt;&lt;/a&gt; here as I think it adds complexity over already quite complex &lt;strong&gt;K8s&lt;/strong&gt; manifests. Surely it might be useful for large-scale stuff, but let's keep things simple here. I have combined knowledge from the articles with the updated charts in order to created a trimmed-down version of the required manifests (it would be good to add &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/" rel="noopener noreferrer"&gt;liveliness and readiness probes&lt;/a&gt; though):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;BITNAMI_DEBUG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;                  &lt;span class="c1"&gt;# Set to "true" for more debug information&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRESQL_VOLUME_DIR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bitnami/postgresql&lt;/span&gt;
  &lt;span class="na"&gt;PGDATA&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bitnami/postgresql/data&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRESQL_LOG_HOSTNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;         &lt;span class="c1"&gt;# Set to "false" for less debug information&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRESQL_LOG_CONNECTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;     &lt;span class="c1"&gt;# Set to "true" for more debug information&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRESQL_CLIENT_MIN_MESSAGES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error"&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRESQL_SHARED_PRELOAD_LIBRARIES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pgaudit,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repmgr"&lt;/span&gt;    &lt;span class="c1"&gt;# Modules being used for replication&lt;/span&gt;
  &lt;span class="na"&gt;REPMGR_LOG_LEVEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NOTICE"&lt;/span&gt;
  &lt;span class="na"&gt;REPMGR_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repmgr&lt;/span&gt;               &lt;span class="c1"&gt;# Replication user&lt;/span&gt;
  &lt;span class="na"&gt;REPMGR_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repmgr&lt;/span&gt;               &lt;span class="c1"&gt;# Replication information database&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-scripts-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Script for pod termination&lt;/span&gt;
  &lt;span class="na"&gt;pre-stop.sh&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|-&lt;/span&gt;
    &lt;span class="s"&gt;#!/bin/bash&lt;/span&gt;
    &lt;span class="s"&gt;set -o errexit&lt;/span&gt;
    &lt;span class="s"&gt;set -o pipefail&lt;/span&gt;
    &lt;span class="s"&gt;set -o nounset&lt;/span&gt;

    &lt;span class="s"&gt;# Debug section&lt;/span&gt;
    &lt;span class="s"&gt;exec 3&amp;gt;&amp;amp;1&lt;/span&gt;
    &lt;span class="s"&gt;exec 4&amp;gt;&amp;amp;2&lt;/span&gt;

    &lt;span class="s"&gt;# Process input parameters&lt;/span&gt;
    &lt;span class="s"&gt;MIN_DELAY_AFTER_PG_STOP_SECONDS=$1&lt;/span&gt;

    &lt;span class="s"&gt;# Load Libraries&lt;/span&gt;
    &lt;span class="s"&gt;. /opt/bitnami/scripts/liblog.sh&lt;/span&gt;
    &lt;span class="s"&gt;. /opt/bitnami/scripts/libpostgresql.sh&lt;/span&gt;
    &lt;span class="s"&gt;. /opt/bitnami/scripts/librepmgr.sh&lt;/span&gt;

    &lt;span class="s"&gt;# Load PostgreSQL &amp;amp; repmgr environment variables&lt;/span&gt;
    &lt;span class="s"&gt;. /opt/bitnami/scripts/postgresql-env.sh&lt;/span&gt;

    &lt;span class="s"&gt;# Auxiliary functions&lt;/span&gt;
    &lt;span class="s"&gt;is_new_primary_ready() {&lt;/span&gt;
        &lt;span class="s"&gt;return_value=1&lt;/span&gt;
        &lt;span class="s"&gt;currenty_primary_node="$(repmgr_get_primary_node)"&lt;/span&gt;
        &lt;span class="s"&gt;currenty_primary_host="$(echo $currenty_primary_node | awk '{print $1}')"&lt;/span&gt;

        &lt;span class="s"&gt;info "$currenty_primary_host != $REPMGR_NODE_NETWORK_NAME"&lt;/span&gt;
        &lt;span class="s"&gt;if [[ $(echo $currenty_primary_node | wc -w) -eq 2 ]] &amp;amp;&amp;amp; [[ "$currenty_primary_host" != "$REPMGR_NODE_NETWORK_NAME" ]]; then&lt;/span&gt;
            &lt;span class="s"&gt;info "New primary detected, leaving the cluster..."&lt;/span&gt;
            &lt;span class="s"&gt;return_value=0&lt;/span&gt;
        &lt;span class="s"&gt;else&lt;/span&gt;
            &lt;span class="s"&gt;info "Waiting for a new primary to be available..."&lt;/span&gt;
        &lt;span class="s"&gt;fi&lt;/span&gt;
        &lt;span class="s"&gt;return $return_value&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;

    &lt;span class="s"&gt;export MODULE="pre-stop-hook"&lt;/span&gt;

    &lt;span class="s"&gt;if [[ "${BITNAMI_DEBUG}" == "true" ]]; then&lt;/span&gt;
        &lt;span class="s"&gt;info "Bash debug is on"&lt;/span&gt;
    &lt;span class="s"&gt;else&lt;/span&gt;
        &lt;span class="s"&gt;info "Bash debug is off"&lt;/span&gt;
        &lt;span class="s"&gt;exec 1&amp;gt;/dev/null&lt;/span&gt;
        &lt;span class="s"&gt;exec 2&amp;gt;/dev/null&lt;/span&gt;
    &lt;span class="s"&gt;fi&lt;/span&gt;

    &lt;span class="s"&gt;postgresql_enable_nss_wrapper&lt;/span&gt;

    &lt;span class="s"&gt;# Prepare env vars for managing roles&lt;/span&gt;
    &lt;span class="s"&gt;readarray -t primary_node &amp;lt; &amp;lt;(repmgr_get_upstream_node)&lt;/span&gt;
    &lt;span class="s"&gt;primary_host="${primary_node[0]}"&lt;/span&gt;

    &lt;span class="s"&gt;# Stop postgresql for graceful exit.&lt;/span&gt;
    &lt;span class="s"&gt;PG_STOP_TIME=$EPOCHSECONDS&lt;/span&gt;
    &lt;span class="s"&gt;postgresql_stop&lt;/span&gt;

    &lt;span class="s"&gt;if [[ -z "$primary_host" ]] || [[ "$primary_host" == "$REPMGR_NODE_NETWORK_NAME" ]]; then&lt;/span&gt;
        &lt;span class="s"&gt;info "Primary node need to wait for a new primary node before leaving the cluster"&lt;/span&gt;
        &lt;span class="s"&gt;retry_while is_new_primary_ready 10 5&lt;/span&gt;
    &lt;span class="s"&gt;else&lt;/span&gt;
        &lt;span class="s"&gt;info "Standby node doesn't need to wait for a new primary switchover. Leaving the cluster"&lt;/span&gt;
    &lt;span class="s"&gt;fi&lt;/span&gt;

    &lt;span class="s"&gt;# Make sure pre-stop hook waits at least 25 seconds after stop of PG to make sure PGPOOL detects node is down.&lt;/span&gt;
    &lt;span class="s"&gt;# default terminationGracePeriodSeconds=30 seconds&lt;/span&gt;
    &lt;span class="s"&gt;PG_STOP_DURATION=$(($EPOCHSECONDS - $PG_STOP_TIME))&lt;/span&gt;
    &lt;span class="s"&gt;if (( $PG_STOP_DURATION &amp;lt; $MIN_DELAY_AFTER_PG_STOP_SECONDS )); then&lt;/span&gt;
        &lt;span class="s"&gt;WAIT_TO_PG_POOL_TIME=$(($MIN_DELAY_AFTER_PG_STOP_SECONDS - $PG_STOP_DURATION)) &lt;/span&gt;
        &lt;span class="s"&gt;info "PG stopped including primary switchover in $PG_STOP_DURATION. Waiting additional $WAIT_TO_PG_POOL_TIME seconds for PG pool"&lt;/span&gt;
        &lt;span class="s"&gt;sleep $WAIT_TO_PG_POOL_TIME&lt;/span&gt;
    &lt;span class="s"&gt;fi&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-secret&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cG9zdGdyZXM=&lt;/span&gt;         &lt;span class="c1"&gt;# Default user(postgres)'s password&lt;/span&gt;
  &lt;span class="na"&gt;REPMGR_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cmVwbWdy&lt;/span&gt;               &lt;span class="c1"&gt;# Replication user's password&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-state&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-service&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                   &lt;span class="c1"&gt;# Container is not run as root&lt;/span&gt;
        &lt;span class="na"&gt;fsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
        &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
        &lt;span class="na"&gt;runAsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
          &lt;span class="na"&gt;lifecycle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;preStop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                     &lt;span class="c1"&gt;# Routines to run before pod termination&lt;/span&gt;
              &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/pre-stop.sh&lt;/span&gt;
                  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;25"&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/bitnami/postgresql-repmgr:16.2.0&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;           
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-port&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-config&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPMGR_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPMGR_PASSWORD&lt;/span&gt;
          &lt;span class="c1"&gt;# Write the pod name (from metadata field) to an environment variable in order to automatically generate replication addresses   &lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POD_NAME&lt;/span&gt;                   
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;fieldRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;fieldPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;metadata.name&lt;/span&gt;
          &lt;span class="c1"&gt;# Repmgr configuration&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPMGR_NAMESPACE&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;fieldRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;fieldPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;metadata.namespace&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPMGR_PARTNER_NODES&lt;/span&gt;          &lt;span class="c1"&gt;# All pods being synchronized (has to reflect the number of replicas)&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-state-0.postgres-service.$(REPMGR_NAMESPACE).svc.cluster.local,postgres-state-1.postgres-service.$(REPMGR_NAMESPACE).svc.cluster.local&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPMGR_PRIMARY_HOST&lt;/span&gt;           &lt;span class="c1"&gt;# Pod with write access. Everybody else replicates it&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-state-0.postgres-service.$(REPMGR_NAMESPACE).svc.cluster.local&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPMGR_NODE_NAME&lt;/span&gt;              &lt;span class="c1"&gt;# Current pod name&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(POD_NAME)&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPMGR_NODE_NETWORK_NAME&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(POD_NAME).postgres-service.$(REPMGR_NAMESPACE).svc.cluster.local&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-db&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bitnami/postgresql&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-scripts&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/pre-stop.sh&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pre-stop.sh&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/tmp&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmp-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/postgresql/conf&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-conf-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/postgresql/tmp&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-tmp-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/repmgr/conf&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repmgr-conf-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/repmgr/tmp&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repmgr-tmp-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/repmgr/logs&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repmgr-logs-dir&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-scripts&lt;/span&gt;
          &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-scripts-config&lt;/span&gt;
            &lt;span class="na"&gt;defaultMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0755&lt;/span&gt;               &lt;span class="c1"&gt;# Access permissions (owner can execute processes)&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;                   &lt;span class="c1"&gt;# Use a fake directory for mounting unused but required paths&lt;/span&gt;
          &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                     &lt;span class="c1"&gt;# Description of volume claim created for each replica&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-db&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-small&lt;/span&gt;
      &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8Gi&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-service&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;                 &lt;span class="c1"&gt;# Default service type&lt;/span&gt;
  &lt;span class="na"&gt;clusterIP&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;None&lt;/span&gt;                 &lt;span class="c1"&gt;# Do not get a service-wide address&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt; 
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the &lt;a href="https://bitnami.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Bitnami&lt;/strong&gt;&lt;/a&gt; container runs as a non-root user for &lt;a href="https://github.com/bitnami/containers/tree/main/bitnami/postgresql-repmgr#why-use-a-non-root-container" rel="noopener noreferrer"&gt;security reasons&lt;/a&gt;, requires a &lt;em&gt;&lt;strong&gt;"postgres"&lt;/strong&gt;&lt;/em&gt; administrator name for the database  and uses a different path structure, you won't be able to mount the data from the original &lt;strong&gt;PostgreSQL&lt;/strong&gt; deployment without messing around with some configuration. So, unless you absolutely need the data, just start from scratch by deleting the volumes, maybe doing a backup first &lt;/p&gt;

&lt;p&gt;Notice how our &lt;strong&gt;ClusterIP&lt;/strong&gt; is set to not have an shared address, making it a &lt;strong&gt;headless&lt;/strong&gt; service, so that it only serves the purpose of exposing the target port of each individual pod, still accessible via &lt;code&gt;&amp;lt;pod name&amp;gt;.&amp;lt;service-name&amp;gt;:&amp;lt;container port number&amp;gt;&lt;/code&gt;. We do that as our containers here are not meant to be accessed in a random or load-balanced manner&lt;/p&gt;

&lt;p&gt;If you're writing your own application, it's easy to define different addresses for writing to and reading from a replicated database, respecting the role of each copy. But a lot of useful software already around assumes a single connection is needed, and there's no simple way to get around that. That's why you need specific intermediaries or proxies like &lt;a href="https://github.com/bitnami/containers/tree/main/bitnami/pgpool" rel="noopener noreferrer"&gt;&lt;strong&gt;Pgpool-II&lt;/strong&gt;&lt;/a&gt; for &lt;strong&gt;PostgreSQL&lt;/strong&gt;, that can appear to applications as a single entity, redirecting queries to the appropriate backend database depending on it's contents:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz3yl62xevf7j7plrdzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdz3yl62xevf7j7plrdzy.png" alt="Postgres vs Postgres-HA" width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
(From the &lt;a href="https://github.com/bitnami/charts/blob/main/bitnami/postgresql-ha/README.md#differences-between-the-postgresql-ha-and-postgresql-helm-charts" rel="noopener noreferrer"&gt;PostgreSQL-HA documentation&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;BITNAMI_DEBUG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_BACKEND_NODES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0:postgres-state-0.postgres-service:5432,1:postgres-state-1.postgres-service:5432&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_SR_CHECK_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repmgr&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_SR_CHECK_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;repmgr&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_POSTGRES_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_ADMIN_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pgpool&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_AUTHENTICATION_METHOD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;scram-sha-256&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_ENABLE_LOAD_BALANCING&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_DISABLE_LOAD_BALANCE_ON_WRITE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;transaction"&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_ENABLE_LOG_CONNECTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_ENABLE_LOG_HOSTNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes"&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_NUM_INIT_CHILDREN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;25"&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_MAX_POOL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8"&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_RESERVED_CONNECTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_HEALTH_CHECK_PSQL_TIMEOUT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy-secret&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;PGPOOL_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cGdwb29s&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-users-secret&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;usernames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dXNlcjEsdXNlcjIsdXNlcjM=&lt;/span&gt;
  &lt;span class="na"&gt;passwords&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cHN3ZDEscHN3ZDIscHN3ZDM=&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy-deploy&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       
      &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;fsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
        &lt;span class="na"&gt;runAsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
        &lt;span class="na"&gt;runAsUser&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/bitnami/pgpool:4&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy-config&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PGPOOL_POSTGRES_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PGPOOL_SR_CHECK_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPMGR_PASSWORD&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PGPOOL_ADMIN_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PGPOOL_ADMIN_PASSWORD&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PGPOOL_POSTGRES_CUSTOM_USERS&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-users-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;usernames&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PGPOOL_POSTGRES_CUSTOM_PASSWORDS&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-users-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;passwords&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pg-proxy-port&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/tmp&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tmp-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/pgpool/etc&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-etc-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/pgpool/conf&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-conf-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/pgpool/tmp&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-tmp-dir&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/opt/bitnami/pgpool/logs&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app-logs-dir&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;empty-dir&lt;/span&gt;
          &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy-service&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;                        &lt;span class="c1"&gt;# Let it be accessible inside the local network&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-proxy&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pg-proxy-port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I bet most of it is self explanatory by now. Just pay extra attention to the &lt;code&gt;NUM_INIT_CHILDREN&lt;/code&gt;, &lt;code&gt;MAX_POLL&lt;/code&gt; and &lt;code&gt;RESERVED_CONNECTIONS&lt;/code&gt; variables and &lt;a href="https://www.pgpool.net/mediawiki/index.php/Relationship_between_max_pool,_num_init_children,_and_max_connections" rel="noopener noreferrer"&gt;the relationship between them&lt;/a&gt;, as their default values may not be appropriate at all for your application and result in too many connection refusals (Been there. Done that). Moreover, users other than administrator and replicator are blocked from access unless you add them to the custom lists of usernames and passwords, in the format &lt;code&gt;user1,user2,user3,..&lt;/code&gt; and &lt;code&gt;pswd1,pswd2,pswd3,...&lt;/code&gt;, here provided as &lt;strong&gt;base64&lt;/strong&gt;-encoded secrets&lt;/p&gt;

&lt;p&gt;With all that configured, we can finally (this time I really mean it) deploy a useful, stateful and replicated application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get all &lt;span class="nt"&gt;-n&lt;/span&gt; choppa &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres                                                                                                                                               
NAME                   READY   STATUS    RESTARTS   AGE
pod/postgres-state-0   1/1     Running   0          40h
pod/postgres-state-1   1/1     Running   0          40h

NAME                       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;    AGE
service/postgres-service   ClusterIP   None         &amp;lt;none&amp;gt;        5432/TCP   40h

&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get all &lt;span class="nt"&gt;-n&lt;/span&gt; choppa &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres-proxy                                                                                                                                         
NAME                                         READY   STATUS    RESTARTS   AGE
pod/postgres-proxy-deploy-74bbdd9b9d-j2tsn   1/1     Running   0          40h

NAME                             TYPE           CLUSTER-IP     EXTERNAL-IP                 PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;          AGE
service/postgres-proxy-service   LoadBalancer   10.43.217.63   192.168.3.10,192.168.3.12   5432:30217/TCP   40h

NAME                                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/postgres-proxy-deploy   1/1     1            1           40h

NAME                                               DESIRED   CURRENT   READY   AGE
replicaset.apps/postgres-proxy-deploy-74bbdd9b9d   1         1         1       40h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Some nice usage of component &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/" rel="noopener noreferrer"&gt;labels&lt;/a&gt; for selection)&lt;/p&gt;

&lt;h3&gt;
  
  
  Into the Breach!
&lt;/h3&gt;

&lt;p&gt;You should be able to view your database you the &lt;code&gt;postgres&lt;/code&gt; user the same way we did last time. After informing the necessary custom users to &lt;strong&gt;Pgpoll&lt;/strong&gt;, now not only I can get my &lt;strong&gt;Telegram&lt;/strong&gt; bridge back running (using the proxy address for the connection string), but also install &lt;strong&gt;WhatsApp&lt;/strong&gt; and &lt;strong&gt;Discord&lt;/strong&gt; ones. Although they're written in &lt;strong&gt;Go&lt;/strong&gt; rather than &lt;strong&gt;Python&lt;/strong&gt;, configuration is very similar, with the relevant parts below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;config.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;# Homeserver details.&lt;/span&gt;
    &lt;span class="s"&gt;homeserver:&lt;/span&gt;
        &lt;span class="s"&gt;# The address that this appservice can use to connect to the homeserver.&lt;/span&gt;
        &lt;span class="s"&gt;address: https://talk.choppa.xyz&lt;/span&gt;
        &lt;span class="s"&gt;# The domain of the homeserver (also known as server_name, used for MXIDs, etc).&lt;/span&gt;
        &lt;span class="s"&gt;domain: choppa.xyz&lt;/span&gt;
        &lt;span class="s"&gt;# ...&lt;/span&gt;
    &lt;span class="s"&gt;# Application service host/registration related details.&lt;/span&gt;
    &lt;span class="s"&gt;# Changing these values requires regeneration of the registration.&lt;/span&gt;
    &lt;span class="s"&gt;appservice:&lt;/span&gt;
        &lt;span class="s"&gt;# The address that the homeserver can use to connect to this appservice.&lt;/span&gt;
        &lt;span class="s"&gt;address: http://whatsapp-service:29318&lt;/span&gt;

        &lt;span class="s"&gt;# The hostname and port where this appservice should listen.&lt;/span&gt;
        &lt;span class="s"&gt;hostname: 0.0.0.0&lt;/span&gt;
        &lt;span class="s"&gt;port: 29318&lt;/span&gt;

        &lt;span class="s"&gt;# Database config.&lt;/span&gt;
        &lt;span class="s"&gt;database:&lt;/span&gt;
            &lt;span class="s"&gt;# The database type. "sqlite3-fk-wal" and "postgres" are supported.&lt;/span&gt;
            &lt;span class="s"&gt;type: postgres&lt;/span&gt;
            &lt;span class="s"&gt;# The database URI.&lt;/span&gt;
            &lt;span class="s"&gt;#   SQLite: A raw file path is supported, but `file:&amp;lt;path&amp;gt;?_txlock=immediate` is recommended.&lt;/span&gt;
            &lt;span class="s"&gt;#           https://github.com/mattn/go-sqlite3#connection-string&lt;/span&gt;
            &lt;span class="s"&gt;#   Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable&lt;/span&gt;
            &lt;span class="s"&gt;#             To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql&lt;/span&gt;
            &lt;span class="s"&gt;uri: postgres://whatsapp:mautrix@postgres-proxy-service/matrix_whatsapp?sslmode=disable&lt;/span&gt;
            &lt;span class="s"&gt;# Maximum number of connections. Mostly relevant for Postgres.&lt;/span&gt;
            &lt;span class="s"&gt;max_open_conns: 20&lt;/span&gt;
            &lt;span class="s"&gt;max_idle_conns: 2&lt;/span&gt;
            &lt;span class="s"&gt;# Maximum connection idle time and lifetime before they're closed. Disabled if null.&lt;/span&gt;
            &lt;span class="s"&gt;# Parsed with https://pkg.go.dev/time#ParseDuration&lt;/span&gt;
            &lt;span class="s"&gt;max_conn_idle_time: null&lt;/span&gt;
            &lt;span class="s"&gt;max_conn_lifetime: null&lt;/span&gt;

        &lt;span class="s"&gt;# The unique ID of this appservice.&lt;/span&gt;
        &lt;span class="s"&gt;id: whatsapp&lt;/span&gt;
        &lt;span class="s"&gt;# Appservice bot details.&lt;/span&gt;
        &lt;span class="s"&gt;bot:&lt;/span&gt;
            &lt;span class="s"&gt;# Username of the appservice bot.&lt;/span&gt;
            &lt;span class="s"&gt;username: whatsappbot&lt;/span&gt;
            &lt;span class="s"&gt;# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty&lt;/span&gt;
            &lt;span class="s"&gt;# to leave display name/avatar as-is.&lt;/span&gt;
            &lt;span class="s"&gt;displayname: WhatsApp bridge bot&lt;/span&gt;
            &lt;span class="s"&gt;avatar: mxc://maunium.net/NeXNQarUbrlYBiPCpprYsRqr&lt;/span&gt;

        &lt;span class="s"&gt;# Whether or not to receive ephemeral events via appservice transactions.&lt;/span&gt;
        &lt;span class="s"&gt;# Requires MSC2409 support (i.e. Synapse 1.22+).&lt;/span&gt;
        &lt;span class="s"&gt;ephemeral_events: true&lt;/span&gt;

        &lt;span class="s"&gt;# Should incoming events be handled asynchronously?&lt;/span&gt;
        &lt;span class="s"&gt;# This may be necessary for large public instances with lots of messages going through.&lt;/span&gt;
        &lt;span class="s"&gt;# However, messages will not be guaranteed to be bridged in the same order they were sent in.&lt;/span&gt;
        &lt;span class="s"&gt;async_transactions: false&lt;/span&gt;

        &lt;span class="s"&gt;# Authentication tokens for AS &amp;lt;-&amp;gt; HS communication. Autogenerated; do not modify.&lt;/span&gt;
        &lt;span class="s"&gt;as_token: &amp;lt;same as token as in registration.yaml&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;hs_token: &amp;lt;same hs token as in registration.yaml&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;# ...&lt;/span&gt;
    &lt;span class="s"&gt;# Config for things that are directly sent to WhatsApp.&lt;/span&gt;
    &lt;span class="s"&gt;whatsapp:&lt;/span&gt;
        &lt;span class="s"&gt;# Device name that's shown in the "WhatsApp Web" section in the mobile app.&lt;/span&gt;
        &lt;span class="s"&gt;os_name: Mautrix-WhatsApp bridge&lt;/span&gt;
        &lt;span class="s"&gt;# Browser name that determines the logo shown in the mobile app.&lt;/span&gt;
        &lt;span class="s"&gt;# Must be "unknown" for a generic icon or a valid browser name if you want a specific icon.&lt;/span&gt;
        &lt;span class="s"&gt;# List of valid browser names: https://github.com/tulir/whatsmeow/blob/efc632c008604016ddde63bfcfca8de4e5304da9/binary/proto/def.proto#L43-L64&lt;/span&gt;
        &lt;span class="s"&gt;browser_name: unknown&lt;/span&gt;
        &lt;span class="s"&gt;# Proxy to use for all WhatsApp connections.&lt;/span&gt;
        &lt;span class="s"&gt;proxy: null&lt;/span&gt;
        &lt;span class="s"&gt;# Alternative to proxy: an HTTP endpoint that returns the proxy URL to use for WhatsApp connections.&lt;/span&gt;
        &lt;span class="s"&gt;get_proxy_url: null&lt;/span&gt;
        &lt;span class="s"&gt;# Whether the proxy options should only apply to the login websocket and not to authenticated connections.&lt;/span&gt;
        &lt;span class="s"&gt;proxy_only_login: false&lt;/span&gt;

    &lt;span class="s"&gt;# Bridge config&lt;/span&gt;
    &lt;span class="s"&gt;bridge:&lt;/span&gt;
        &lt;span class="s"&gt;# ...&lt;/span&gt;
        &lt;span class="s"&gt;# Settings for handling history sync payloads.&lt;/span&gt;
        &lt;span class="s"&gt;history_sync:&lt;/span&gt;
            &lt;span class="s"&gt;# Enable backfilling history sync payloads from WhatsApp?&lt;/span&gt;
            &lt;span class="s"&gt;backfill: true&lt;/span&gt;
            &lt;span class="s"&gt;# ...&lt;/span&gt;
            &lt;span class="s"&gt;# Shared secret for authentication. If set to "generate", a random secret will be generated,&lt;/span&gt;
            &lt;span class="s"&gt;# or if set to "disable", the provisioning API will be disabled.&lt;/span&gt;
            &lt;span class="s"&gt;shared_secret: generate&lt;/span&gt;
            &lt;span class="s"&gt;# Enable debug API at /debug with provisioning authentication.&lt;/span&gt;
            &lt;span class="s"&gt;debug_endpoints: false&lt;/span&gt;

        &lt;span class="s"&gt;# Permissions for using the bridge.&lt;/span&gt;
        &lt;span class="s"&gt;# Permitted values:&lt;/span&gt;
        &lt;span class="s"&gt;#    relay - Talk through the relaybot (if enabled), no access otherwise&lt;/span&gt;
        &lt;span class="s"&gt;#     user - Access to use the bridge to chat with a WhatsApp account.&lt;/span&gt;
        &lt;span class="s"&gt;#    admin - User level and some additional administration tools&lt;/span&gt;
        &lt;span class="s"&gt;# Permitted keys:&lt;/span&gt;
        &lt;span class="s"&gt;#        * - All Matrix users&lt;/span&gt;
        &lt;span class="s"&gt;#   domain - All users on that homeserver&lt;/span&gt;
        &lt;span class="s"&gt;#     mxid - Specific user&lt;/span&gt;
        &lt;span class="s"&gt;permissions:&lt;/span&gt;
            &lt;span class="s"&gt;"*": relay&lt;/span&gt;
            &lt;span class="s"&gt;"@ancapepe:choppa.xyz": admin&lt;/span&gt;
            &lt;span class="s"&gt;"@ancompepe:choppa.xyz": user&lt;/span&gt;
        &lt;span class="s"&gt;# Settings for relay mode&lt;/span&gt;
        &lt;span class="s"&gt;relay:&lt;/span&gt;
            &lt;span class="s"&gt;# Whether relay mode should be allowed. If allowed, `!wa set-relay` can be used to turn any&lt;/span&gt;
            &lt;span class="s"&gt;# authenticated user into a relaybot for that chat.&lt;/span&gt;
            &lt;span class="s"&gt;enabled: false&lt;/span&gt;
            &lt;span class="s"&gt;# Should only admins be allowed to set themselves as relay users?&lt;/span&gt;
            &lt;span class="s"&gt;admin_only: true&lt;/span&gt;
            &lt;span class="s"&gt;# ...&lt;/span&gt;
    &lt;span class="s"&gt;# Logging config. See https://github.com/tulir/zeroconfig for details.&lt;/span&gt;
    &lt;span class="s"&gt;logging:&lt;/span&gt;
        &lt;span class="s"&gt;min_level: debug&lt;/span&gt;
        &lt;span class="s"&gt;writers:&lt;/span&gt;
        &lt;span class="s"&gt;- type: stdout&lt;/span&gt;
          &lt;span class="s"&gt;format: pretty-colored&lt;/span&gt;
  &lt;span class="na"&gt;registration.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;id: whatsapp&lt;/span&gt;
    &lt;span class="s"&gt;url: http://whatsapp-service:29318&lt;/span&gt;
    &lt;span class="s"&gt;as_token: &amp;lt;same as token as in config.yaml&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;hs_token: &amp;lt;same hs token as in config.yaml&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;sender_localpart: SH98XxA4xvgFtlbx1NxJm9VYW6q3BdYg&lt;/span&gt;
    &lt;span class="s"&gt;rate_limited: false&lt;/span&gt;
    &lt;span class="s"&gt;namespaces:&lt;/span&gt;
        &lt;span class="s"&gt;users:&lt;/span&gt;
            &lt;span class="s"&gt;- regex: ^@whatsappbot:choppa\.xyz$&lt;/span&gt;
              &lt;span class="s"&gt;exclusive: true&lt;/span&gt;
            &lt;span class="s"&gt;- regex: ^@whatsapp_.*:choppa\.xyz$&lt;/span&gt;
              &lt;span class="s"&gt;exclusive: true&lt;/span&gt;
    &lt;span class="s"&gt;de.sorunome.msc2409.push_ephemeral: true&lt;/span&gt;
    &lt;span class="s"&gt;push_ephemeral: true&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-deploy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dock.mau.dev/mautrix/whatsapp:latest&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/mautrix-whatsapp"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/config.yaml"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-r"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/registration.yaml"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--no-update"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29318&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-port&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/config.yaml&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config.yaml&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/registration.yaml&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registration.yaml&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-volume&lt;/span&gt;
        &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-config&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publishNotReadyAddresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29318&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;whatsapp-port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;config.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;# Homeserver details.&lt;/span&gt;
    &lt;span class="s"&gt;homeserver:&lt;/span&gt;
        &lt;span class="s"&gt;# The address that this appservice can use to connect to the homeserver.&lt;/span&gt;
        &lt;span class="s"&gt;address: https://talk.choppa.xyz&lt;/span&gt;
        &lt;span class="s"&gt;# The domain of the homeserver (also known as server_name, used for MXIDs, etc).&lt;/span&gt;
        &lt;span class="s"&gt;domain: choppa.xyz&lt;/span&gt;

        &lt;span class="s"&gt;# What software is the homeserver running?&lt;/span&gt;
        &lt;span class="s"&gt;# Standard Matrix homeservers like Synapse, Dendrite and Conduit should just use "standard" here.&lt;/span&gt;
        &lt;span class="s"&gt;software: standard&lt;/span&gt;
        &lt;span class="s"&gt;# The URL to push real-time bridge status to.&lt;/span&gt;
        &lt;span class="s"&gt;# If set, the bridge will make POST requests to this URL whenever a user's discord connection state changes.&lt;/span&gt;
        &lt;span class="s"&gt;# The bridge will use the appservice as_token to authorize requests.&lt;/span&gt;
        &lt;span class="s"&gt;status_endpoint: null&lt;/span&gt;
        &lt;span class="s"&gt;# Endpoint for reporting per-message status.&lt;/span&gt;
        &lt;span class="s"&gt;message_send_checkpoint_endpoint: null&lt;/span&gt;
        &lt;span class="s"&gt;# Does the homeserver support https://github.com/matrix-org/matrix-spec-proposals/pull/2246?&lt;/span&gt;
        &lt;span class="s"&gt;async_media: false&lt;/span&gt;

        &lt;span class="s"&gt;# Should the bridge use a websocket for connecting to the homeserver?&lt;/span&gt;
        &lt;span class="s"&gt;# The server side is currently not documented anywhere and is only implemented by mautrix-wsproxy,&lt;/span&gt;
        &lt;span class="s"&gt;# mautrix-asmux (deprecated), and hungryserv (proprietary).&lt;/span&gt;
        &lt;span class="s"&gt;websocket: false&lt;/span&gt;
        &lt;span class="s"&gt;# How often should the websocket be pinged? Pinging will be disabled if this is zero.&lt;/span&gt;
        &lt;span class="s"&gt;ping_interval_seconds: 0&lt;/span&gt;

    &lt;span class="s"&gt;# Application service host/registration related details.&lt;/span&gt;
    &lt;span class="s"&gt;# Changing these values requires regeneration of the registration.&lt;/span&gt;
    &lt;span class="s"&gt;appservice:&lt;/span&gt;
        &lt;span class="s"&gt;# The address that the homeserver can use to connect to this appservice.&lt;/span&gt;
        &lt;span class="s"&gt;address: http://discord-service:29334&lt;/span&gt;

        &lt;span class="s"&gt;# The hostname and port where this appservice should listen.&lt;/span&gt;
        &lt;span class="s"&gt;hostname: 0.0.0.0&lt;/span&gt;
        &lt;span class="s"&gt;port: 29334&lt;/span&gt;

        &lt;span class="s"&gt;# Database config.&lt;/span&gt;
        &lt;span class="s"&gt;database:&lt;/span&gt;
            &lt;span class="s"&gt;# The database type. "sqlite3-fk-wal" and "postgres" are supported.&lt;/span&gt;
            &lt;span class="s"&gt;type: postgres&lt;/span&gt;
            &lt;span class="s"&gt;# The database URI.&lt;/span&gt;
            &lt;span class="s"&gt;#   SQLite: A raw file path is supported, but `file:&amp;lt;path&amp;gt;?_txlock=immediate` is recommended.&lt;/span&gt;
            &lt;span class="s"&gt;#           https://github.com/mattn/go-sqlite3#connection-string&lt;/span&gt;
            &lt;span class="s"&gt;#   Postgres: Connection string. For example, postgres://user:password@host/database?sslmode=disable&lt;/span&gt;
            &lt;span class="s"&gt;#             To connect via Unix socket, use something like postgres:///dbname?host=/var/run/postgresql&lt;/span&gt;
            &lt;span class="s"&gt;uri: postgres://discord:mautrix@postgres-proxy-service/matrix_discord?sslmode=disable&lt;/span&gt;
            &lt;span class="s"&gt;# Maximum number of connections. Mostly relevant for Postgres.&lt;/span&gt;
            &lt;span class="s"&gt;max_open_conns: 20&lt;/span&gt;
            &lt;span class="s"&gt;max_idle_conns: 2&lt;/span&gt;
            &lt;span class="s"&gt;# Maximum connection idle time and lifetime before they're closed. Disabled if null.&lt;/span&gt;
            &lt;span class="s"&gt;# Parsed with https://pkg.go.dev/time#ParseDuration&lt;/span&gt;
            &lt;span class="s"&gt;max_conn_idle_time: null&lt;/span&gt;
            &lt;span class="s"&gt;max_conn_lifetime: null&lt;/span&gt;

        &lt;span class="s"&gt;# The unique ID of this appservice.&lt;/span&gt;
        &lt;span class="s"&gt;id: discord&lt;/span&gt;
        &lt;span class="s"&gt;# Appservice bot details.&lt;/span&gt;
        &lt;span class="s"&gt;bot:&lt;/span&gt;
            &lt;span class="s"&gt;# Username of the appservice bot.&lt;/span&gt;
            &lt;span class="s"&gt;username: discordbot&lt;/span&gt;
            &lt;span class="s"&gt;# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty&lt;/span&gt;
            &lt;span class="s"&gt;# to leave display name/avatar as-is.&lt;/span&gt;
            &lt;span class="s"&gt;displayname: Discord bridge bot&lt;/span&gt;
            &lt;span class="s"&gt;avatar: mxc://maunium.net/nIdEykemnwdisvHbpxflpDlC&lt;/span&gt;

        &lt;span class="s"&gt;# Whether or not to receive ephemeral events via appservice transactions.&lt;/span&gt;
        &lt;span class="s"&gt;# Requires MSC2409 support (i.e. Synapse 1.22+).&lt;/span&gt;
        &lt;span class="s"&gt;ephemeral_events: true&lt;/span&gt;

        &lt;span class="s"&gt;# Should incoming events be handled asynchronously?&lt;/span&gt;
        &lt;span class="s"&gt;# This may be necessary for large public instances with lots of messages going through.&lt;/span&gt;
        &lt;span class="s"&gt;# However, messages will not be guaranteed to be bridged in the same order they were sent in.&lt;/span&gt;
        &lt;span class="s"&gt;async_transactions: false&lt;/span&gt;

        &lt;span class="s"&gt;# Authentication tokens for AS &amp;lt;-&amp;gt; HS communication. Autogenerated; do not modify.&lt;/span&gt;
        &lt;span class="s"&gt;as_token: &amp;lt;same as token as in registration.yaml&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;hs_token: &amp;lt;same hs token as in registration.yaml&amp;gt;&lt;/span&gt;

    &lt;span class="s"&gt;# Bridge config&lt;/span&gt;
    &lt;span class="s"&gt;bridge:&lt;/span&gt;
        &lt;span class="s"&gt;# ...&lt;/span&gt;
        &lt;span class="s"&gt;# The prefix for commands. Only required in non-management rooms.&lt;/span&gt;
        &lt;span class="s"&gt;command_prefix: '!discord'&lt;/span&gt;
        &lt;span class="s"&gt;# Permissions for using the bridge.&lt;/span&gt;
        &lt;span class="s"&gt;# Permitted values:&lt;/span&gt;
        &lt;span class="s"&gt;#    relay - Talk through the relaybot (if enabled), no access otherwise&lt;/span&gt;
        &lt;span class="s"&gt;#     user - Access to use the bridge to chat with a Discord account.&lt;/span&gt;
        &lt;span class="s"&gt;#    admin - User level and some additional administration tools&lt;/span&gt;
        &lt;span class="s"&gt;# Permitted keys:&lt;/span&gt;
        &lt;span class="s"&gt;#        * - All Matrix users&lt;/span&gt;
        &lt;span class="s"&gt;#   domain - All users on that homeserver&lt;/span&gt;
        &lt;span class="s"&gt;#     mxid - Specific user&lt;/span&gt;
        &lt;span class="s"&gt;permissions:&lt;/span&gt;
            &lt;span class="s"&gt;"*": relay&lt;/span&gt;
            &lt;span class="s"&gt;"@ancapepe:choppa.xyz": admin&lt;/span&gt;
            &lt;span class="s"&gt;"@ancompepe:choppa.xyz": user&lt;/span&gt;

    &lt;span class="s"&gt;# Logging config. See https://github.com/tulir/zeroconfig for details.&lt;/span&gt;
    &lt;span class="s"&gt;logging:&lt;/span&gt;
        &lt;span class="s"&gt;min_level: debug&lt;/span&gt;
        &lt;span class="s"&gt;writers:&lt;/span&gt;
        &lt;span class="s"&gt;- type: stdout&lt;/span&gt;
          &lt;span class="s"&gt;format: pretty-colored&lt;/span&gt;
  &lt;span class="na"&gt;registration.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;id: discord&lt;/span&gt;
    &lt;span class="s"&gt;url: http://discord-service:29334&lt;/span&gt;
    &lt;span class="s"&gt;as_token: &amp;lt;same as token as in config.yaml&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;hs_token: &amp;lt;same hs token as in config.yaml&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;sender_localpart: KYmI12PCMJuHvD9VZw1cUzMlV7nUezH2&lt;/span&gt;
    &lt;span class="s"&gt;rate_limited: false&lt;/span&gt;
    &lt;span class="s"&gt;namespaces:&lt;/span&gt;
        &lt;span class="s"&gt;users:&lt;/span&gt;
            &lt;span class="s"&gt;- regex: ^@discordbot:choppa\.xyz$&lt;/span&gt;
              &lt;span class="s"&gt;exclusive: true&lt;/span&gt;
            &lt;span class="s"&gt;- regex: ^@discord_.*:choppa\.xyz$&lt;/span&gt;
              &lt;span class="s"&gt;exclusive: true&lt;/span&gt;
    &lt;span class="s"&gt;de.sorunome.msc2409.push_ephemeral: true&lt;/span&gt;
    &lt;span class="s"&gt;push_ephemeral: true&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-deploy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dock.mau.dev/mautrix/discord:latest&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/mautrix-discord"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/config.yaml"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-r"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/registration.yaml"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--no-update"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29334&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-port&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/config.yaml&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config.yaml&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/registration.yaml&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registration.yaml&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-volume&lt;/span&gt;
        &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-config&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publishNotReadyAddresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29334&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;discord-port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Logging into your account will still change depending on the service being bridged. As always, consult &lt;a href="https://docs.mau.fi/bridges/python/setup.html" rel="noopener noreferrer"&gt;the official documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We may forget for a while the multiple opened messaging windows just to communicate with our peers. That river has been crossed!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbr4n4spd2t4f0wg6kcsk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbr4n4spd2t4f0wg6kcsk.png" alt="Nheko communities" width="410" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>kubernetes</category>
      <category>censorship</category>
      <category>decentralization</category>
    </item>
    <item>
      <title>The Home Server Journey - 5: Rebuilding Burned Bridges</title>
      <dc:creator>Beppe</dc:creator>
      <pubDate>Sun, 29 Sep 2024 16:34:21 +0000</pubDate>
      <link>https://dev.to/beppe90/the-home-server-journey-5-rebuilding-burned-bridges-6n7</link>
      <guid>https://dev.to/beppe90/the-home-server-journey-5-rebuilding-burned-bridges-6n7</guid>
      <description>&lt;p&gt;Hello again. It's been a while (in relative terms)&lt;/p&gt;

&lt;p&gt;Having our own &lt;strong&gt;Matrix&lt;/strong&gt; rooms feels amazing indeed, but left us too isolated, at least until we manage to bring people in. I know that &lt;a href="https://dev.to/ancapepe/the-home-server-journey-1-motivation-and-approach-5fhj"&gt;in the beginning&lt;/a&gt; I've warned about making sacrifices, but it doesn't have to be that hard. In life some metaphorical burned bridges are better left unrepaired, but here let's try reconnecting to our old Internet life in an interesting way, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Notes on Infrastructure
&lt;/h3&gt;

&lt;p&gt;I have waited a bit to resume publishing because there were 2 things bothering me about my setup: power reliability and storage coupling&lt;/p&gt;

&lt;p&gt;I don't live near a big urban center, and power oscillations or outages are pretty common here. Having those causing frequent downtime on my server would be quite annoying, so I went after a &lt;strong&gt;nobreak&lt;/strong&gt; to ensure more energy stability for my devices&lt;/p&gt;

&lt;p&gt;With that out the way, as I have said on &lt;a href="https://dev.to/ancapepe/the-home-server-journey-4-enter-the-matrix-55ki"&gt;the previous article&lt;/a&gt;, ideally your &lt;strong&gt;K8s&lt;/strong&gt; system would have an external and dedicated storage provider. I certainly wasn't satisfied with mixing &lt;strong&gt;K8s&lt;/strong&gt; node and &lt;strong&gt;NFS&lt;/strong&gt; server functionality in the same machines, but the ones I was using were the only units with high-bandwith &lt;strong&gt;USB 3.0&lt;/strong&gt; ports, desirable for external drives. So I've decided to invest a bit more and acquire an extra &lt;strong&gt;Raspberry Pi 5&lt;/strong&gt; for my cluster, leaving the &lt;strong&gt;RPi4&lt;/strong&gt; for data management&lt;/p&gt;

&lt;p&gt;Not only the &lt;strong&gt;RPi5&lt;/strong&gt; gives me more performance, but it also &lt;a href="https://www.cnx-software.com/2023/11/05/raspberry-pi-5-review-raspberry-pi-os-bookworm-benchmarks-power-consumption/" rel="noopener noreferrer"&gt;is more equivalent to the &lt;strong&gt;Odroid N2+&lt;/strong&gt;&lt;/a&gt;. As it comes with a beefier power supply (5V-5A), intended for video applications that I'm not doing, I've tried using it for the &lt;strong&gt;RPi4&lt;/strong&gt; connected to the current-hungry hard drives. However, &lt;a href="https://forums.raspberrypi.com/viewtopic.php?t=364252" rel="noopener noreferrer"&gt;its circuit is simply not made to use all that juice&lt;/a&gt;, so as a last piece of the puzzle I had to get an externally powered &lt;strong&gt;USB3&lt;/strong&gt; hub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3ejgeixz94tnphs3f9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk3ejgeixz94tnphs3f9q.png" alt="New server hardware"&gt;&lt;/a&gt;&lt;br&gt;
(The resulting &lt;em&gt;Frankenstein&lt;/em&gt; is not a pretty sight)&lt;/p&gt;

&lt;p&gt;If that mess of a wiring is too hard to understand (I bet it is), have a quick diagram of how it works below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f00k7gi4k9dw3o575y5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f00k7gi4k9dw3o575y5.png" alt="Server connections"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;At first I was worried that using a single &lt;strong&gt;USB&lt;/strong&gt; port for 2 disks would be a bottleneck, but even in the best case, sequential reading, HDDs cannot saturate the 5Gbps bandwidth the &lt;strong&gt;RPi4&lt;/strong&gt; can handle on that interface, and the 1Gbps Ethernet will be a greater constraint anyway&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Keeping the State, Orchestrated Style
&lt;/h3&gt;

&lt;p&gt;One thing that you notice when deploying all sorts of applications is how much they rely on databases, &lt;a href="https://en.wikipedia.org/wiki/Relational_database" rel="noopener noreferrer"&gt;&lt;strong&gt;relational&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/NoSQL" rel="noopener noreferrer"&gt;&lt;strong&gt;non-relational&lt;/strong&gt;&lt;/a&gt;, in order to delegate internal data management and remain &lt;strong&gt;stateless&lt;/strong&gt;. That's true to the point that we can hardly progress here without having some &lt;strong&gt;DBMS&lt;/strong&gt; running first&lt;/p&gt;

&lt;p&gt;It's common to have databases running as off-node services just like &lt;strong&gt;NFS&lt;/strong&gt;, but the ability to escalate them horizontally is then lost. Here, we'll resort to &lt;strong&gt;Kubernetes&lt;/strong&gt; &lt;strong&gt;stateful&lt;/strong&gt; features in order to keep data management inside the cluster. For that, instead of a &lt;strong&gt;Deployment&lt;/strong&gt; we require a &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/" rel="noopener noreferrer"&gt;&lt;strong&gt;StatefulSet&lt;/strong&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-config&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppadb&lt;/span&gt;                   &lt;span class="c1"&gt;# Default database&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin&lt;/span&gt;                    &lt;span class="c1"&gt;# Default user&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-secret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Z290eW91&lt;/span&gt;             &lt;span class="c1"&gt;# Default user's password&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;                         &lt;span class="c1"&gt;# Component type&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-state&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-service&lt;/span&gt;           &lt;span class="c1"&gt;# Do not give a service name to each replica&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16.4&lt;/span&gt;              &lt;span class="c1"&gt;# Current stable version at 17.0, but let's be a bit conservative&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-port&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-config&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/postgresql/data&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-db&lt;/span&gt;
  &lt;span class="na"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                     &lt;span class="c1"&gt;# Description of volume claim created for each replica&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-db&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-small&lt;/span&gt;
      &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
      &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5Gi&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-service&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;                        &lt;span class="c1"&gt;# Let it be accessible inside the local network&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5432&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(At first we're using a &lt;a href="https://www.postgresql.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt;&lt;/a&gt; image from &lt;a href="https://hub.docker.com/_/postgres/" rel="noopener noreferrer"&gt;the official &lt;strong&gt;DockerHub&lt;/strong&gt; repository&lt;/a&gt;, but different databases such as &lt;a href="https://www.mysql.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;MySQL&lt;/strong&gt;&lt;/a&gt;/&lt;a href="https://mariadb.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;MariaDB&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://www.mongodb.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;MongoDB&lt;/strong&gt;&lt;/a&gt; will follow a similar pattern)&lt;/p&gt;

&lt;p&gt;The biggest difference from previous deployments is that we don't explicitly define a &lt;strong&gt;Persistent Volume Claim&lt;/strong&gt; to be referenced by all replicas, but give the &lt;strong&gt;StatefulSet&lt;/strong&gt; a template on how to request a distinct storage area for each Pod. Moreover, the &lt;code&gt;serviceName&lt;/code&gt; configuration defines how stateful pods will get their specific hostnames from the internal &lt;strong&gt;DNS&lt;/strong&gt;, in the format &lt;code&gt;&amp;lt;pod name&amp;gt;.&amp;lt;service-name&amp;gt;:&amp;lt;container port number&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpbvkrl9xvoq9fi966q74.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpbvkrl9xvoq9fi966q74.png" alt="Stateful pods"&gt;&lt;/a&gt;&lt;br&gt;
(I have tried using a stateful configuration for &lt;strong&gt;Conduit&lt;/strong&gt; as well, but also wasn't able to increase the number of replicas due to some authentication issues)&lt;/p&gt;

&lt;p&gt;Notice how those pods follow a different naming scheme. &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/" rel="noopener noreferrer"&gt;&lt;strong&gt;ReplicaSets&lt;/strong&gt;&lt;/a&gt; with persisting data changes have to be created, deleted and updated/synchronized in a very particular order, therefore they cannot be as ephemeral as stateless ones, with asynchronously managed and randomly named containers. Those constraints are the reason we should avoid &lt;strong&gt;StatefulSets&lt;/strong&gt; wherever possible &lt;/p&gt;
&lt;h3&gt;
  
  
  A Telegram from an Old Friend
&lt;/h3&gt;

&lt;p&gt;As I've promised, today we're learning how to keep in touch with people in the centralized world from our new &lt;strong&gt;Matrix&lt;/strong&gt; home. And that's achieved through the power of &lt;a href="https://github.com/mautrix" rel="noopener noreferrer"&gt;&lt;strong&gt;Mautrix&lt;/strong&gt; bridges&lt;/a&gt;. As I'm a big user of &lt;strong&gt;Telegram&lt;/strong&gt;, that's what I'll be using as an example, but the process is mostly the same for other services, like &lt;strong&gt;Discord&lt;/strong&gt; or &lt;strong&gt;WhatsApp&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Long story short, the bridge needs to be configured on 2 ends: how to interact with your third-party service account and how to register onto your &lt;strong&gt;Matrix&lt;/strong&gt; server. The templates for those settings may be automatically generated using the same container image we'll me pulling into our &lt;strong&gt;K8s&lt;/strong&gt; cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a directory for configuration files and enter it&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;:/data:z dock.mau.dev/mautrix/telegram:latest                                                                                                
Didn&lt;span class="s1"&gt;'t find a config file.
Copied default config file to /data/config.yaml
Modify that config file to your liking.
Start the container again after that to generate the registration file.
# Change the configuration file to match your requirements and preferences
$ docker run --rm -v `pwd`:/data:z dock.mau.dev/mautrix/telegram:latest                                                                                               
Registration generated and saved to /data/registration.yaml
Didn'&lt;/span&gt;t find a registration file.
Generated one &lt;span class="k"&gt;for &lt;/span&gt;you.
See https://docs.mau.fi/bridges/general/registering-appservices.html on how to use it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you either manually add the contents of &lt;code&gt;config.yaml&lt;/code&gt; and &lt;code&gt;registration.yaml&lt;/code&gt; to a &lt;strong&gt;ConfigMap&lt;/strong&gt; manifest file or automatically generate/print it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl create configmap telegram-config &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./config.yaml &lt;span class="nt"&gt;--from-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;./registration.yaml &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Running as super user is required as the folder permissions are modified. &lt;code&gt;--dry-run&lt;/code&gt; prevents the configuration from being directly applied, so that you may adjust possible mistakes, pipe the results to a &lt;strong&gt;YAML&lt;/strong&gt; file or add the result to a bigger manifest)&lt;/p&gt;

&lt;p&gt;Bridge configuration is quite long, so I'll reduce it below to only the most relevant parts. For information on all options, consult &lt;a href="https://docs.mau.fi/bridges/index.html" rel="noopener noreferrer"&gt;the official documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-config&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;config.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;# Homeserver details&lt;/span&gt;
    &lt;span class="s"&gt;homeserver:&lt;/span&gt;
        &lt;span class="s"&gt;# The address that this appservice can use to connect to the homeserver.&lt;/span&gt;
        &lt;span class="s"&gt;address: http://conduit-service:8448&lt;/span&gt;
        &lt;span class="s"&gt;# The domain of the homeserver (for MXIDs, etc).&lt;/span&gt;
        &lt;span class="s"&gt;domain: choppa.xyz&lt;/span&gt;
    &lt;span class="s"&gt;# ...&lt;/span&gt;
    &lt;span class="s"&gt;# Application service host/registration related details&lt;/span&gt;
    &lt;span class="s"&gt;# Changing these values requires regeneration of the registration.&lt;/span&gt;
    &lt;span class="s"&gt;appservice:&lt;/span&gt;
        &lt;span class="s"&gt;# The address that the homeserver can use to connect to this appservice.&lt;/span&gt;
        &lt;span class="s"&gt;address: http://telegram-service:29317&lt;/span&gt;
        &lt;span class="s"&gt;# When using https:// the TLS certificate and key files for the address.&lt;/span&gt;
        &lt;span class="s"&gt;tls_cert: false&lt;/span&gt;
        &lt;span class="s"&gt;tls_key: false&lt;/span&gt;
        &lt;span class="s"&gt;# The hostname and port where this appservice should listen.&lt;/span&gt;
        &lt;span class="s"&gt;hostname: 0.0.0.0&lt;/span&gt;
        &lt;span class="s"&gt;port: 29317&lt;/span&gt;
        &lt;span class="s"&gt;# ...&lt;/span&gt;
        &lt;span class="s"&gt;# The full URI to the database. SQLite and Postgres are supported.&lt;/span&gt;
        &lt;span class="s"&gt;# Format examples:&lt;/span&gt;
        &lt;span class="s"&gt;#   SQLite:   sqlite:filename.db&lt;/span&gt;
        &lt;span class="s"&gt;#   Postgres: postgres://username:password@hostname/dbname&lt;/span&gt;
        &lt;span class="s"&gt;database: postgres://telegram:mautrix@postgres-service/matrix_telegram&lt;/span&gt;
        &lt;span class="s"&gt;# ...&lt;/span&gt;
        &lt;span class="s"&gt;# Authentication tokens for AS &amp;lt;-&amp;gt; HS communication. Autogenerated; do not modify.&lt;/span&gt;
        &lt;span class="s"&gt;as_token: &amp;lt;same as token from registration.yaml&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;hs_token: &amp;lt;same hs token from registration.yaml&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;# ...&lt;/span&gt;
    &lt;span class="s"&gt;# Bridge config&lt;/span&gt;
    &lt;span class="s"&gt;bridge:&lt;/span&gt;
        &lt;span class="s"&gt;# ...&lt;/span&gt;
        &lt;span class="s"&gt;# Shared secrets for https://github.com/devture/matrix-synapse-shared-secret-auth&lt;/span&gt;
        &lt;span class="s"&gt;#&lt;/span&gt;
        &lt;span class="s"&gt;# If set, custom puppets will be enabled automatically for local users&lt;/span&gt;
        &lt;span class="s"&gt;# instead of users having to find an access token and run `login-matrix`&lt;/span&gt;
        &lt;span class="s"&gt;# manually.&lt;/span&gt;
        &lt;span class="s"&gt;# If using this for other servers than the bridge's server,&lt;/span&gt;
        &lt;span class="s"&gt;# you must also set the URL in the double_puppet_server_map.&lt;/span&gt;
        &lt;span class="s"&gt;login_shared_secret_map:&lt;/span&gt;
            &lt;span class="s"&gt;choppa.xyz: &amp;lt;your access token&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;# ...&lt;/span&gt;
    &lt;span class="s"&gt;# ...&lt;/span&gt;
    &lt;span class="s"&gt;# Telegram config&lt;/span&gt;
    &lt;span class="s"&gt;telegram:&lt;/span&gt;
        &lt;span class="s"&gt;# Get your own API keys at https://my.telegram.org/apps&lt;/span&gt;
        &lt;span class="s"&gt;api_id: &amp;lt;id you have generated&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;api_hash: &amp;lt;hash you have generated&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;# (Optional) Create your own bot at https://t.me/BotFather&lt;/span&gt;
        &lt;span class="s"&gt;bot_token: disabled&lt;/span&gt;
        &lt;span class="s"&gt;# ...&lt;/span&gt;
  &lt;span class="na"&gt;registration.yaml&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;id: telegram&lt;/span&gt;
    &lt;span class="s"&gt;as_token: &amp;lt;same as token from config.yaml&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;hs_token: &amp;lt;same hs token from config.yaml&amp;gt;&lt;/span&gt;
    &lt;span class="s"&gt;namespaces:&lt;/span&gt;
        &lt;span class="s"&gt;users:&lt;/span&gt;
        &lt;span class="s"&gt;- exclusive: true&lt;/span&gt;
          &lt;span class="s"&gt;regex: '@telegram_.*:choppa\.xyz'&lt;/span&gt;
        &lt;span class="s"&gt;- exclusive: true&lt;/span&gt;
          &lt;span class="s"&gt;regex: '@telegrambot:choppa\.xyz'&lt;/span&gt;
        &lt;span class="s"&gt;aliases:&lt;/span&gt;
        &lt;span class="s"&gt;- exclusive: true&lt;/span&gt;
          &lt;span class="s"&gt;regex: \#telegram_.*:choppa\.xyz&lt;/span&gt;
    &lt;span class="s"&gt;url: http://telegram-service:29317&lt;/span&gt;
    &lt;span class="s"&gt;sender_localpart: HyMPlWT_552RAGkFtnuy_ZNNpkKrSSaHDndS_nmb9VIZ4RiJLH0uiSas3fi_IV_x&lt;/span&gt;
    &lt;span class="s"&gt;rate_limited: false&lt;/span&gt;
    &lt;span class="s"&gt;de.sorunome.msc2409.push_ephemeral: true&lt;/span&gt;
    &lt;span class="s"&gt;push_ephemeral: true&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-deploy&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dock.mau.dev/mautrix/telegram:latest&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;
          &lt;span class="c1"&gt;# Custom container initialization command (overrides the one defined in the image)&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-m"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mautrix_telegram"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/config.yaml"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-r"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/data/registration.yaml"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--no-update"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29317&lt;/span&gt;      &lt;span class="c1"&gt;# Has to match the appservice configuration&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-port&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/config.yaml&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;config.yaml&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-volume&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/data/registration.yaml&lt;/span&gt;
              &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registration.yaml&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-volume&lt;/span&gt;
        &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-config&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publishNotReadyAddresses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;29317&lt;/span&gt;               &lt;span class="c1"&gt;# Has to match the registration url&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegram-port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying the changes, the bridge pod is deployed, but it keeps failing and restarting... Why?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrb73989w9eeyykp2676.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrb73989w9eeyykp2676.png" alt="Bridge failure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Visualizing the logs give us a clearer picture of what's happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl logs telegram-deploy-84489fb64d-srmrc &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;choppa                                                                                                                         ✔  default ⎈ 
&lt;span class="o"&gt;[&lt;/span&gt;2024-09-29 15:28:34,871] &lt;span class="o"&gt;[&lt;/span&gt;INFO@mau.init] Initializing mautrix-telegram 0.15.2
&lt;span class="o"&gt;[&lt;/span&gt;2024-09-29 15:28:34,879] &lt;span class="o"&gt;[&lt;/span&gt;INFO@mau.init] Initialization &lt;span class="nb"&gt;complete &lt;/span&gt;&lt;span class="k"&gt;in &lt;/span&gt;0.19 seconds
&lt;span class="o"&gt;[&lt;/span&gt;2024-09-29 15:28:34,879] &lt;span class="o"&gt;[&lt;/span&gt;DEBUG@mau.init] Running startup actions...
&lt;span class="o"&gt;[&lt;/span&gt;2024-09-29 15:28:34,879] &lt;span class="o"&gt;[&lt;/span&gt;DEBUG@mau.init] Starting database...
&lt;span class="o"&gt;[&lt;/span&gt;2024-09-29 15:28:34,879] &lt;span class="o"&gt;[&lt;/span&gt;DEBUG@mau.db] Connecting to postgres://telegram:password-redacted@postgres-service/matrix_telegram
&lt;span class="o"&gt;[&lt;/span&gt;2024-09-29 15:28:34,946] &lt;span class="o"&gt;[&lt;/span&gt;CRITICAL@mau.init] Failed to initialize database
Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/mautrix/bridge/bridge.py"&lt;/span&gt;, line 216, &lt;span class="k"&gt;in &lt;/span&gt;start_db
    await self.db.start&lt;span class="o"&gt;()&lt;/span&gt;
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/mautrix/util/async_db/asyncpg.py"&lt;/span&gt;, line 71, &lt;span class="k"&gt;in &lt;/span&gt;start
    self._pool &lt;span class="o"&gt;=&lt;/span&gt; await asyncpg.create_pool&lt;span class="o"&gt;(&lt;/span&gt;str&lt;span class="o"&gt;(&lt;/span&gt;self.url&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;**&lt;/span&gt;self._db_args&lt;span class="o"&gt;)&lt;/span&gt;
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/asyncpg/pool.py"&lt;/span&gt;, line 403, &lt;span class="k"&gt;in &lt;/span&gt;_async__init__
    await self._initialize&lt;span class="o"&gt;()&lt;/span&gt;
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/asyncpg/pool.py"&lt;/span&gt;, line 430, &lt;span class="k"&gt;in &lt;/span&gt;_initialize
    await first_ch.connect&lt;span class="o"&gt;()&lt;/span&gt;
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/asyncpg/pool.py"&lt;/span&gt;, line 128, &lt;span class="k"&gt;in &lt;/span&gt;connect
    self._con &lt;span class="o"&gt;=&lt;/span&gt; await self._pool._get_new_connection&lt;span class="o"&gt;()&lt;/span&gt;
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/asyncpg/pool.py"&lt;/span&gt;, line 502, &lt;span class="k"&gt;in &lt;/span&gt;_get_new_connection
    con &lt;span class="o"&gt;=&lt;/span&gt; await connection.connect&lt;span class="o"&gt;(&lt;/span&gt;
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/asyncpg/connection.py"&lt;/span&gt;, line 2329, &lt;span class="k"&gt;in &lt;/span&gt;connect
    &lt;span class="k"&gt;return &lt;/span&gt;await connect_utils._connect&lt;span class="o"&gt;(&lt;/span&gt;
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/asyncpg/connect_utils.py"&lt;/span&gt;, line 991, &lt;span class="k"&gt;in &lt;/span&gt;_connect
    conn &lt;span class="o"&gt;=&lt;/span&gt; await _connect_addr&lt;span class="o"&gt;(&lt;/span&gt;
           ^^^^^^^^^^^^^^^^^^^^
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/asyncpg/connect_utils.py"&lt;/span&gt;, line 828, &lt;span class="k"&gt;in &lt;/span&gt;_connect_addr
    &lt;span class="k"&gt;return &lt;/span&gt;await __connect_addr&lt;span class="o"&gt;(&lt;/span&gt;params, True, &lt;span class="k"&gt;*&lt;/span&gt;args&lt;span class="o"&gt;)&lt;/span&gt;
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File &lt;span class="s2"&gt;"/usr/lib/python3.11/site-packages/asyncpg/connect_utils.py"&lt;/span&gt;, line 876, &lt;span class="k"&gt;in &lt;/span&gt;__connect_addr
    await connected
asyncpg.exceptions.InvalidPasswordError: password authentication failed &lt;span class="k"&gt;for &lt;/span&gt;user &lt;span class="s2"&gt;"telegram"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;2024-09-29 15:28:34,950] &lt;span class="o"&gt;[&lt;/span&gt;DEBUG@mau.init] Stopping database due to SystemExit
&lt;span class="o"&gt;[&lt;/span&gt;2024-09-29 15:28:34,950] &lt;span class="o"&gt;[&lt;/span&gt;DEBUG@mau.init] Database stopped

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you pay attention to the configuration shown, there's a database section where you may either configure &lt;a href="https://www.sqlite.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;SQLite&lt;/strong&gt;&lt;/a&gt; (local file, let's avoid that) or &lt;strong&gt;PostgreSQL&lt;/strong&gt; usage, required by the bridge. I wouldn't go on a tangent about databases for nothing, right?&lt;/p&gt;

&lt;p&gt;The problem is that the connection string defined, which follows the format &lt;code&gt;postgres://&amp;lt;user&amp;gt;:&amp;lt;password&amp;gt;@&amp;lt;server hostname&amp;gt;/&amp;lt;database name&amp;gt;&lt;/code&gt;, is referencing a user and a database that don't exist (I wouldn't use the administrator account for a simple bot). So let's fix that by logging as the main user into the &lt;strong&gt;DBMS&lt;/strong&gt; with a &lt;strong&gt;PostgreSQL&lt;/strong&gt;-compatible client of your choice. Here I'm using &lt;a href="https://sqlectron.github.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;SQLectron&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5eos3bv8p9ra1f5860i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5eos3bv8p9ra1f5860i.png" alt="SQLectron"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From there, use this sequence of &lt;a href="https://www.w3schools.com/sql/sql_intro.asp" rel="noopener noreferrer"&gt;&lt;strong&gt;SQL&lt;/strong&gt;&lt;/a&gt; commands that are pretty much self-explanatory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;matrix_telegram&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;PASSWORD&lt;/span&gt; &lt;span class="s1"&gt;'mautrix'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;CONNECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;matrix_telegram&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Personal note: ENTER THE DATABASE !!&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="n"&gt;SEQUENCES&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Give priveleges for all newly created tables as well&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(A bit overkill but, hey, works for me... If some commands fail you may need to wait a bit for the pods to synchronize or even reduce the number of replicas to 1 while making changes)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Errata: Once again we cannot use stateful features to the fullest. Replicating data across pods is not done automatically by the &lt;strong&gt;K8s&lt;/strong&gt; system and dependends on the application. It took me a while to figure that out and I'll have to study how to do it properly for each case. For now we'll keep it at only one instance and address proper replication later&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you still get some log errors about an unknown token, go into the admin room of your &lt;strong&gt;Conduit&lt;/strong&gt; server and send this message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gls98w1tzqhaouz25z1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2gls98w1tzqhaouz25z1.png" alt="Registering appservice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, start a private chat with &lt;code&gt;@telegrambot&amp;lt;your server name&amp;gt;&lt;/code&gt; (default name, may be changed in the config) and follow the login procedure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkswo9lah0f63lhn26rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkswo9lah0f63lhn26rw.png" alt="Telegram bot login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Invites for your &lt;strong&gt;Telegram&lt;/strong&gt; groups and private chats should start appearing, and now you can be happy again!&lt;/p&gt;

&lt;p&gt;Thanks for reading. See you next time&lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>kubernetes</category>
      <category>censorship</category>
      <category>decentralization</category>
    </item>
    <item>
      <title>The Home Server Journey - 4: Enter The Matrix</title>
      <dc:creator>Beppe</dc:creator>
      <pubDate>Fri, 20 Sep 2024 23:24:25 +0000</pubDate>
      <link>https://dev.to/beppe90/the-home-server-journey-4-enter-the-matrix-55ki</link>
      <guid>https://dev.to/beppe90/the-home-server-journey-4-enter-the-matrix-55ki</guid>
      <description>&lt;p&gt;Hello&lt;/p&gt;

&lt;p&gt;Time to forget about &lt;a href="https://dev.to/ancapepe/the-home-server-journey-3-an-actually-global-hello-2af6"&gt;simple demo containers&lt;/a&gt; and struggle [not so much] with the configuration details of fully-featured applications. Today we not only put &lt;strong&gt;Kubernetes&lt;/strong&gt; to production-level usage, but find out about some strengths and weaknesses of &lt;a href="https://en.wikipedia.org/wiki/Distributed_computing" rel="noopener noreferrer"&gt;&lt;strong&gt;distributed computing&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Notes on storage
&lt;/h3&gt;

&lt;p&gt;Code nowadays, more than ever, works consuming and producing huge amounts of data, that usually has (or "have", since "data" is plural for "datum"?) to be kept for later. It's no surprise then that the capacity to store information is one of the most valuable commodities in IT, always at risk of being depleted due to poor optimization&lt;/p&gt;

&lt;p&gt;In the containerized world, where application instances can be created and recreated dynamically, extra steps have to be taken for reusing disk space and avoiding duplication whenever possible. That's why &lt;strong&gt;K8s&lt;/strong&gt; adopts a layered approach: deployments don't create &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/" rel="noopener noreferrer"&gt;&lt;strong&gt;Persistent Volumes&lt;/strong&gt;&lt;/a&gt;, roughly equivalent to root folders, on their own, but set their storage requirements via &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#binding" rel="noopener noreferrer"&gt;&lt;strong&gt;Persistent Volume Claims&lt;/strong&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Long story short, administrators either create the necessary volumes manually or let the claims be managed by a &lt;a href="https://kubernetes.io/docs/concepts/storage/storage-classes/" rel="noopener noreferrer"&gt;&lt;strong&gt;Storage Class&lt;/strong&gt;&lt;/a&gt;, to provide volumes on demand. Like ingresses have multiple controller implementations, depending on the backend, storage classes allow for different providers, offering a variety of &lt;strong&gt;local&lt;/strong&gt; and &lt;strong&gt;remote&lt;/strong&gt; (including &lt;strong&gt;cloud&lt;/strong&gt;) data resources:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjeeybd47u8xrd0qiy5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjeeybd47u8xrd0qiy5p.png" alt="Nana's tutorial" width="800" height="544"&gt;&lt;/a&gt;&lt;br&gt;
(Screenshot from &lt;a href="https://www.youtube.com/watch?v=X48VuDVv0do" rel="noopener noreferrer"&gt;Nana's tutorial&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Although local is an easy to use option, it quickly betrays the purpose of container orchestration: tying storage to a specific machine's disk path prevents multiple pod replicas from being distributed among multiple nodes for &lt;a href="https://en.wikipedia.org/wiki/Load_balancing_(computing)" rel="noopener noreferrer"&gt;load balancing&lt;/a&gt;. Besides, if we wish to avoid cloud providers (like &lt;strong&gt;Google&lt;/strong&gt; or &lt;strong&gt;AWS&lt;/strong&gt;) to have full control of our data, we're left with [local] network storage solutions like &lt;a href="https://wiki.archlinux.org/title/NFS" rel="noopener noreferrer"&gt;&lt;strong&gt;NFS&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you remember &lt;a href="https://dev.to/ancapepe/the-home-server-journey-1-motivation-and-approach-5fhj"&gt;the first article&lt;/a&gt;, my setup consists of one &lt;strong&gt;USB&lt;/strong&gt; hard disk (meant for persistent volumes) connected to each node board. That's not ideal for &lt;strong&gt;NFS&lt;/strong&gt;, as decoupling storage from the &lt;strong&gt;K8s&lt;/strong&gt; cluster, using a dedicated computer, would prevent node crashes compromising data availability to the rest. Still, I can at least make the devices share disks with each other&lt;/p&gt;

&lt;p&gt;In order to expose external drive's partition to the network, begin by ensuring that they're mounted during initialization by editing &lt;code&gt;/etc/fstab&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Finding the partition's UUID (assuming a EXT4-formatted disk)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;lsblk &lt;span class="nt"&gt;-f&lt;/span&gt;
NAME        FSTYPE FSVER LABEL      UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sda                                                                                     
└─sda1      ext4   1.0              69c7df98-0349-4c47-84ce-514a1699ccf1  869.2G     0% 
mmcblk0                                                                                 
├─mmcblk0p1 vfat   FAT16 BOOT_MNJRO 42D4-5413                             394.8M    14% /boot
└─mmcblk0p2 ext4   1.0   ROOT_MNJRO 83982167-1add-4b6a-ad63-3479493a4510   44.4G    18% /var/lib/kubelet/pods/ae500dcf-b765-4f37-be8c-ee20fbb3ea1b/volume-subpaths/welcome-volume/welcome/0
                                                                                        /
zram0                                                                                   &lt;span class="o"&gt;[&lt;/span&gt;SWAP]
&lt;span class="c"&gt;# Creating the mount point for the external partition&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /storage
&lt;span class="c"&gt;# Adding line with mount configuration to fstab &lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'UUID=69c7df98-0349-4c47-84ce-514a1699ccf1 /storage ext4 defaults,noatime        0       2'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/fstab
&lt;span class="c"&gt;# Check the contents&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/fstab
&lt;span class="c"&gt;# Static information about the filesystems.&lt;/span&gt;
&lt;span class="c"&gt;# See fstab(5) for details.&lt;/span&gt;

&lt;span class="c"&gt;# &amp;lt;file system&amp;gt; &amp;lt;dir&amp;gt; &amp;lt;type&amp;gt; &amp;lt;options&amp;gt; &amp;lt;dump&amp;gt; &amp;lt;pass&amp;gt;&lt;/span&gt;
&lt;span class="nv"&gt;PARTUUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;42045b6f-01  /boot   vfat    defaults,noexec,nodev,showexec     0   0
&lt;span class="nv"&gt;PARTUUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;42045b6f-02   /   ext4     defaults    0   1
&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;69c7df98-0349-4c47-84ce-514a1699ccf1 /storage ext4 defaults,noatime        0       2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reboot and check if you can access the contents of the &lt;code&gt;/storage&lt;/code&gt; folder (or whatever path you've chosen). Proceed by setting up the &lt;strong&gt;NFS&lt;/strong&gt; server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install required packages (showing the ones for Manjaro Linux)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;pacman &lt;span class="nt"&gt;-S&lt;/span&gt; nfs-utils rpcbind
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="c"&gt;# Installation process ...&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;span class="c"&gt;# Add disk sharing configurations (Adapt to you local network address range)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/storage    192.168.3.0/24(rw,sync,no_wdelay,no_root_squash,insecure)'&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; /etc/exports
&lt;span class="c"&gt;# Check the contents&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/exports
&lt;span class="c"&gt;# /etc/exports - exports(5) - directories exported to NFS clients&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Example for NFSv3:&lt;/span&gt;
&lt;span class="c"&gt;#  /srv/home        hostname1(rw,sync) hostname2(ro,sync)&lt;/span&gt;
&lt;span class="c"&gt;# Example for NFSv4:&lt;/span&gt;
&lt;span class="c"&gt;#  /srv/nfs4        hostname1(rw,sync,fsid=0)&lt;/span&gt;
&lt;span class="c"&gt;#  /srv/nfs4/home   hostname1(rw,sync,nohide)&lt;/span&gt;
&lt;span class="c"&gt;# Using Kerberos and integrity checking:&lt;/span&gt;
&lt;span class="c"&gt;#  /srv/nfs4        *(rw,sync,sec=krb5i,fsid=0)&lt;/span&gt;
&lt;span class="c"&gt;#  /srv/nfs4/home   *(rw,sync,sec=krb5i,nohide)&lt;/span&gt;
&lt;span class="c"&gt;#&lt;/span&gt;
&lt;span class="c"&gt;# Use `exportfs -arv` to reload.&lt;/span&gt;
/storage    192.168.3.0/24&lt;span class="o"&gt;(&lt;/span&gt;rw,sync,no_wdelay,no_root_squash,insecure&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# Update exports based on configuration&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;exportfs &lt;span class="nt"&gt;-arv&lt;/span&gt;
&lt;span class="c"&gt;# Enable and start related initialization services&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; rpcbind nfs-server 
&lt;span class="c"&gt;# Check if shared folders are visible&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;showmount localhost &lt;span class="nt"&gt;-e&lt;/span&gt;
Export list &lt;span class="k"&gt;for &lt;/span&gt;localhost:
/storage 192.168.3.0/24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Remember that your mileage may vary)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Phew&lt;/em&gt;, and that's just the first part of it! Not only you have to do the same process for every machine whose storage you wish to share, but afterwards it's necessary to install a &lt;strong&gt;NFS&lt;/strong&gt; provisioner to the &lt;strong&gt;K8s&lt;/strong&gt; cluster. Here I have used &lt;a href="https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner" rel="noopener noreferrer"&gt;&lt;strong&gt;nfs-subdir-external-provisioner&lt;/strong&gt;&lt;/a&gt;, not as-it-is, but modifying 2 manifest files to my liking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modified &lt;code&gt;deploy/rbac.yaml&lt;/code&gt; (Access permissions):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner&lt;/span&gt;
  &lt;span class="c1"&gt;# replace with namespace where provisioner is deployed&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-storage&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner-runner&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nodes"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;persistentvolumes"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delete"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;persistentvolumeclaims"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;storage.k8s.io"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;storageclasses"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRoleBinding&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;run-nfs-client-provisioner&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner&lt;/span&gt;
    &lt;span class="c1"&gt;# replace with namespace where provisioner is deployed&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-storage&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner-runner&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;leader-locking-nfs-client-provisioner&lt;/span&gt;
  &lt;span class="c1"&gt;# replace with namespace where provisioner is deployed&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-storage&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;endpoints"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RoleBinding&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;leader-locking-nfs-client-provisioner&lt;/span&gt;
  &lt;span class="c1"&gt;# replace with namespace where provisioner is deployed&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-storage&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner&lt;/span&gt;
    &lt;span class="c1"&gt;# replace with namespace where provisioner is deployed&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-storage&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;leader-locking-nfs-client-provisioner&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Here just set the namespace to your liking)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modified &lt;code&gt;deploy/deployment.yaml&lt;/code&gt; (Storage classes):
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner&lt;/span&gt;
  &lt;span class="c1"&gt;# replace with namespace where provisioner is deployed&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-storage&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recreate&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;serviceAccountName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# 1TB disk on Odroid N2+&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner-big&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-root-big&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/persistentvolumes&lt;/span&gt;   &lt;span class="c1"&gt;# CAN'T CHANGE THIS PATH&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;# Environment variables&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PROVISIONER_NAME&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-provisioner-big&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NFS_SERVER&lt;/span&gt;                &lt;span class="c1"&gt;# Hostnames don't resolve in setting env vars&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.3.10&lt;/span&gt;             &lt;span class="c1"&gt;# Looking for a way to make it more flexible&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NFS_PATH&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/storage&lt;/span&gt;
        &lt;span class="c1"&gt;# 500GB disk on Raspberry Pi 4B&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client-provisioner-small&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-root-small&lt;/span&gt;
              &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/persistentvolumes&lt;/span&gt;   &lt;span class="c1"&gt;# CAN'T CHANGE THIS PATH&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;# Environment variables&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PROVISIONER_NAME&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-provisioner-small&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NFS_SERVER&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.3.11&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NFS_PATH&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/storage&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-root-big&lt;/span&gt;
          &lt;span class="na"&gt;nfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.3.10&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/storage&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-root-small&lt;/span&gt;
          &lt;span class="na"&gt;nfs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;192.168.3.11&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/storage&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# 1TB disk on Odroid N2+&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;storage.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StorageClass&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-big&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
&lt;span class="na"&gt;provisioner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-provisioner-big&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;archiveOnDelete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
&lt;span class="na"&gt;reclaimPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retain&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# 500GB disk on Raspberry Pi 4B&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;storage.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StorageClass&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-small&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
&lt;span class="na"&gt;provisioner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-provisioner-small&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;archiveOnDelete&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
&lt;span class="na"&gt;reclaimPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retain&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Make sure that all namespace settings match the RBAC rules. Also notice that, the way the provisioner works, I had to create on storage class for each shared disk)&lt;/p&gt;

&lt;p&gt;If everything goes according to plan, applying a test like the one below will make volumes appear to meet the claims and the &lt;code&gt;SUCCESS&lt;/code&gt; file to be written on each one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-claim-big&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-big&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteMany&lt;/span&gt;          &lt;span class="c1"&gt;# Concurrent access&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1Mi&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-claim-small&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-small&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteMany&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1Mi&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-pod-big&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-pod-big&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox:stable&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;               &lt;span class="c1"&gt;# Replace the default command for the image by this&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/sh"&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                  &lt;span class="c1"&gt;# Custom command arguments&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;touch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/mnt/SUCCESS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
    &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-volume&lt;/span&gt;
        &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt"&lt;/span&gt;
  &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Never"&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-volume&lt;/span&gt;
      &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-claim-big&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-pod-small&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-pod-small&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox:stable&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/sh"&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;touch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/mnt/SUCCESS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
    &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-volume&lt;/span&gt;
        &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt"&lt;/span&gt;
  &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Never"&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-volume&lt;/span&gt;
      &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-claim-small&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Thanks to Albert Weng for &lt;a href="https://weng-albert.medium.com/how-to-create-an-nfs-storageclass-en-fe962242f44e" rel="noopener noreferrer"&gt;the very useful guide&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  Your Very Own Messaging Service
&lt;/h3&gt;

&lt;p&gt;Now that we have real storage at our disposal, some real apps would be nice. One of the first things that come to mind when I think about data I'd like to protect is the history of my private conversations, where most spontaneous interactions happen and most ideas get forgotten&lt;/p&gt;

&lt;p&gt;If you're a old school person wishing to host your own messaging, I bet you'd go for &lt;a href="https://en.wikipedia.org/wiki/IRC" rel="noopener noreferrer"&gt;&lt;strong&gt;IRC&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://xmpp.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;XMPP&lt;/strong&gt;&lt;/a&gt;. But I'm more of a late millennial, so it's easier to get drawn to fancy stuff like &lt;a href="https://matrix.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Matrix&lt;/strong&gt;&lt;/a&gt;, a protocol for federated communication&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;"And what's federation?"&lt;/em&gt;&lt;/strong&gt;, you may ask. Well, to this day, the best video about it I've seen is the one from &lt;a href="https://odysee.com/@SimplyExplained:4e" rel="noopener noreferrer"&gt;&lt;strong&gt;Simply Explained&lt;/strong&gt;&lt;/a&gt;. Even though it's focused on &lt;strong&gt;Mastodon&lt;/strong&gt; and the &lt;strong&gt;Fediverse&lt;/strong&gt; (We'll get there eventually), the same concepts apply:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/S57uhCQBEk0"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;As any open protocol, the &lt;a href="https://spec.matrix.org/latest/" rel="noopener noreferrer"&gt;&lt;strong&gt;Matrix&lt;/strong&gt; specification&lt;/a&gt; has a number of different implementations, most famously &lt;a href="https://github.com/matrix-org/synapse" rel="noopener noreferrer"&gt;&lt;strong&gt;Synapse&lt;/strong&gt;&lt;/a&gt; (Python) and &lt;a href="https://github.com/matrix-org/dendrite" rel="noopener noreferrer"&gt;&lt;strong&gt;Dendrite&lt;/strong&gt;&lt;/a&gt; (Go). I've been, however, particularly endeared to &lt;a href="https://conduit.rs/" rel="noopener noreferrer"&gt;&lt;strong&gt;Conduit&lt;/strong&gt;&lt;/a&gt;, a lightweight &lt;strong&gt;Matrix&lt;/strong&gt; server written in &lt;strong&gt;Rust&lt;/strong&gt;. Here I'll show the deployment configuration as recommended by its maintainer, &lt;a href="https://gitlab.com/timokoesters" rel="noopener noreferrer"&gt;&lt;strong&gt;Timo Kösters&lt;/strong&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;       &lt;span class="c1"&gt;# Storage requirements component&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-pv-claim&lt;/span&gt;          
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-big&lt;/span&gt;       &lt;span class="c1"&gt;# The used storage class (1TB drive)&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;               &lt;span class="c1"&gt;# For synchronous access, as we don't want data corruption&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;50Gi&lt;/span&gt;               &lt;span class="c1"&gt;# Asking for a ~50 Gigabytes volume&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;                   &lt;span class="c1"&gt;# Read-only data component&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-config&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_CONFIG&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;              &lt;span class="c1"&gt;# Leave empty to inform that we're not using a configuration file&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_SERVER_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;choppa.xyz'&lt;/span&gt;                   &lt;span class="c1"&gt;# The server name that will appear after registered usernames &lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_DATABASE_PATH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/var/lib/matrix-conduit/'&lt;/span&gt;   &lt;span class="c1"&gt;# Database directory path. Uses the permanent volume&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_DATABASE_BACKEND&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rocksdb'&lt;/span&gt;                 &lt;span class="c1"&gt;# Recommended for performance, sqlite alternative&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_ADDRESS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0'&lt;/span&gt;                          &lt;span class="c1"&gt;# Listen on all IP addresses/Network interfaces&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_PORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;6167'&lt;/span&gt;                                &lt;span class="c1"&gt;# Listening port for the process inside the container&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_MAX_CONCURRENT_REQUESTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;200'&lt;/span&gt;              &lt;span class="c1"&gt;# Network usage limiting options&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_MAX_REQUEST_SIZE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20000000'&lt;/span&gt;                &lt;span class="c1"&gt;# (in bytes, ~20 MB)&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_ALLOW_REGISTRATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;                  &lt;span class="c1"&gt;# Allow other people to register in the server (requires token)&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_ALLOW_FEDERATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;                    &lt;span class="c1"&gt;# Allow communication with other instances&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_ALLOW_CHECK_FOR_UPDATES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;   
  &lt;span class="na"&gt;CONDUIT_TRUSTED_SERVERS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;["matrix.org"]'&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_ALLOW_WELL_KNOWN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;                      &lt;span class="c1"&gt;# Generate "/.well-known/matrix/{client,server}" endpoints&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_WELL_KNOWN_CLIENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://talk.choppa.xyz'&lt;/span&gt;  &lt;span class="c1"&gt;# return value for "/.well-known/matrix/client"&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_WELL_KNOWN_SERVER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;talk.choppa.xyz:443'&lt;/span&gt;      &lt;span class="c1"&gt;# return value for "/.well-known/matrix/server"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;                      &lt;span class="c1"&gt;# Read-only encrypted data&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-secret&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;CONDUIT_REGISTRATION_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bXlwYXNzd29yZG9yYWNjZXNzdG9rZW4=&lt;/span&gt;    &lt;span class="c1"&gt;# Registration token/password in base64 format&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-deploy&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;                     &lt;span class="c1"&gt;# Keep it at 1 as the database has an access lock&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Recreate&lt;/span&gt;                &lt;span class="c1"&gt;# Wait for replica to be terminated completely befor creating a new one&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ancapepe/conduit:next&lt;/span&gt; &lt;span class="c1"&gt;# fork of matrixconduit/matrix-conduit:next&lt;/span&gt;
          &lt;span class="na"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IfNotPresent"&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;6167&lt;/span&gt;                 &lt;span class="c1"&gt;# Has to match the configuration above&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-port&lt;/span&gt;
          &lt;span class="na"&gt;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                &lt;span class="c1"&gt;# Take all data from a config file &lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                       &lt;span class="c1"&gt;# As environment variables&lt;/span&gt;
                &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-config&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                    &lt;span class="c1"&gt;# Set environment variables one by one&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CONDUIT_REGISTRATION_TOKEN&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                     &lt;span class="c1"&gt;# Take data from a secret&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-secret&lt;/span&gt;            &lt;span class="c1"&gt;# Secret source&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CONDUIT_REGISTRATION_TOKEN&lt;/span&gt; &lt;span class="c1"&gt;# Data entry key&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/matrix-conduit/&lt;/span&gt; &lt;span class="c1"&gt;# Mount the referenced volume into this path (database)&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rocksdb&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rocksdb&lt;/span&gt;                           &lt;span class="c1"&gt;# Label the volume for this deployment&lt;/span&gt;
          &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-pv-claim&lt;/span&gt;           &lt;span class="c1"&gt;# Reference volumen create by the claim&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-service&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8448&lt;/span&gt;                                  &lt;span class="c1"&gt;# Using the default port for federation (not required)&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing you should know is that the K8s architecture is optimized for &lt;a href="https://www.redhat.com/en/topics/cloud-native-apps/stateful-vs-stateless" rel="noopener noreferrer"&gt;&lt;strong&gt;stateless applications&lt;/strong&gt;&lt;/a&gt;, which don't store changing information within themselves and whose output depend solely on input from the user or auxiliary processes. &lt;strong&gt;Conduit&lt;/strong&gt;, on the contrary, is tightly coupled with its high-performance database, &lt;a href="https://rocksdb.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;RocksDB&lt;/strong&gt;&lt;/a&gt;, and has &lt;strong&gt;stateful&lt;/strong&gt; behavior. That's why we need to take extra care by not replicating our process in order to prevent &lt;a href="https://www.mathworks.com/products/polyspace/static-analysis-notes/what-data-races-how-avoid-during-software-development.html" rel="noopener noreferrer"&gt;data races&lt;/a&gt; when accessing storage&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There's actually a way to run multiple replicas of a stateful pod, that requires different component types to create one volume copy for each container and keep all them in sync. But let's keep it "simple" for now. More on that in a future chapter&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We introduce here a new component type, &lt;a href="https://kubernetes.io/docs/concepts/configuration/secret/" rel="noopener noreferrer"&gt;&lt;strong&gt;Secret&lt;/strong&gt;&lt;/a&gt;, intended for sensible information such as passwords and access tokens. It requires that all data is set in a &lt;a href="https://www.base64encode.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Base64&lt;/strong&gt;-encoded&lt;/a&gt; format for obfuscation, which could be achieved with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; mypasswordoraccesstoken | &lt;span class="nb"&gt;base64                                                                                                                                                            
&lt;/span&gt;&lt;span class="nv"&gt;bXlwYXNzd29yZG9yYWNjZXNzdG9rZW4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You might also have noticed that I jumped the gun a bit and acquired a proper domain for myself (so that my &lt;strong&gt;Matrix&lt;/strong&gt; server looks nice, of course). I've found one available at a promotional price on &lt;a href="https://www.dynadot.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Dynadot&lt;/strong&gt;&lt;/a&gt;, which now seems even more like a good decision, considering &lt;a href="https://www.techdirt.com/2024/09/18/extwitters-brazil-ban-evasion-cloudflare-cdn-becomes-latest-battleground/" rel="noopener noreferrer"&gt;how easy is to bend Cloudflare&lt;/a&gt;. Conveniently, the registrar also provides a DNS service, so... 2 for the price of one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xblncydbwyx78jqa9vp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xblncydbwyx78jqa9vp.png" alt="Dynadot DNS" width="670" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/List_of_DNS_record_types" rel="noopener noreferrer"&gt;&lt;strong&gt;CNAME&lt;/strong&gt;&lt;/a&gt; record is basically an alias: the subdomain &lt;code&gt;talk.choppa.xyz&lt;/code&gt; is simply redirected to &lt;code&gt;choppa.xyz&lt;/code&gt; and just works as a hint for our &lt;strong&gt;reverse proxy&lt;/strong&gt; from the previous chapter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;                    
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;                                     &lt;span class="c1"&gt;# Component type&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;                                     &lt;span class="c1"&gt;# Component name&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa&lt;/span&gt;                               &lt;span class="c1"&gt;# You may add the default namespace for components as a paramenter&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cert-manager.io/cluster-issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;letsencrypt&lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;loadBalancer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;choppa.xyz&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;talk.choppa.xyz&lt;/span&gt;
    &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certificate&lt;/span&gt;      
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                          &lt;span class="c1"&gt;# Routing rules&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppa.xyz&lt;/span&gt;                              &lt;span class="c1"&gt;# Expected domain name of request, including subdomain&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                         &lt;span class="c1"&gt;# For HTTP or HTTPS requests&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                      &lt;span class="c1"&gt;# Behavior for different base paths&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;                                 &lt;span class="c1"&gt;# For all request paths&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-service&lt;/span&gt;               &lt;span class="c1"&gt;# Redirect to this service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;                      &lt;span class="c1"&gt;# Redirect to this internal service port&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/.well-known/matrix/&lt;/span&gt;              &lt;span class="c1"&gt;# Paths for Conduit delegation&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ImplementationSpecific&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8448&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;talk.choppa.xyz&lt;/span&gt;               &lt;span class="c1"&gt;# Conduit server subdomain&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;conduit-service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8448&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;code&gt;CONDUIT_SERVER_NAME&lt;/code&gt; differs from the [sub]domain where your server is hosted, other &lt;strong&gt;Matrix&lt;/strong&gt; federated instances will look for the actual address in &lt;a href="https://en.wikipedia.org/wiki/Well-known_URI" rel="noopener noreferrer"&gt;&lt;strong&gt;Well-known URIs&lt;/strong&gt;&lt;/a&gt;. Normally you'd have to serve them manually, but &lt;strong&gt;Conduit&lt;/strong&gt; has a convenient &lt;a href="https://docs.conduit.rs/delegation.html" rel="noopener noreferrer"&gt;automatic delegation&lt;/a&gt; feature that helps your set it up with the &lt;code&gt;CONDUIT_WELL_KNOWN_*&lt;/code&gt; env variables&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Currently, at version &lt;code&gt;0.8&lt;/code&gt;, automatically delegation via env vars is not working properly. Timo has helped me debug it (Thanks!) at &lt;a href="https://matrix.to/#/#conduit:fachschaften.org" rel="noopener noreferrer"&gt;Conduit's Matrix room&lt;/a&gt; and I have submitted a &lt;a href="https://gitlab.com/famedly/conduit/-/merge_requests/718" rel="noopener noreferrer"&gt;patch&lt;/a&gt;. Let's hope it gets fixed for &lt;code&gt;0.9&lt;/code&gt;. Meanwhile, you may use the custom image I'm referencing in the configuration example&lt;/p&gt;

&lt;p&gt;Update: Aaaaaand... it's merged!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With ALL that said, let's apply the deployment and hope for the best:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzsrpv8wy6sbvepkg5wjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzsrpv8wy6sbvepkg5wjf.png" alt="Conduit running" width="800" height="663"&gt;&lt;/a&gt;&lt;br&gt;
(Success !?)&lt;/p&gt;

&lt;p&gt;With your instance running, you can now use &lt;strong&gt;Matrix&lt;/strong&gt; clients [that support registration tokens] like &lt;a href="https://element.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Element&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://nheko-reborn.github.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Nheko&lt;/strong&gt;&lt;/a&gt; to register your user, login and have fun!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18tteovs8d3pjzbddd9p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F18tteovs8d3pjzbddd9p.png" alt="Element login" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See you soon &lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>kubernetes</category>
      <category>censorship</category>
      <category>decentralization</category>
    </item>
    <item>
      <title>The Home Server Journey - 3: An Actually Global "Hello"</title>
      <dc:creator>Beppe</dc:creator>
      <pubDate>Wed, 18 Sep 2024 12:48:32 +0000</pubDate>
      <link>https://dev.to/beppe90/the-home-server-journey-3-an-actually-global-hello-2af6</link>
      <guid>https://dev.to/beppe90/the-home-server-journey-3-an-actually-global-hello-2af6</guid>
      <description>&lt;p&gt;Hi again&lt;/p&gt;

&lt;p&gt;For starters, I apologize for setting up the wrong expectations: &lt;a href="https://dev.to/ancapepe/the-home-server-journey-1-motivation-and-approach-5fhj"&gt;&lt;strong&gt;chapter 1&lt;/strong&gt;&lt;/a&gt; anticipated all that stuff about &lt;strong&gt;external IPs&lt;/strong&gt; and &lt;strong&gt;name servers&lt;/strong&gt; [like it would be immediately necessary], but event in &lt;a href="https://dev.to/ancapepe/the-home-server-journey-2-the-control-room-fng"&gt;&lt;strong&gt;chapter 2&lt;/strong&gt;&lt;/a&gt; we still only accessed applications inside the &lt;strong&gt;local network&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my defense, I wished to make all the prerequisites clear from the get-go, so that there where no surprises about what you were getting into. Anyway, let's address that today, sooner rather than later, shall we?&lt;/p&gt;

&lt;h3&gt;
  
  
  Your Virtual Home Receptionist
&lt;/h3&gt;

&lt;p&gt;The reason why you can't simply type &lt;code&gt;http://&amp;lt;my WAN IP&amp;gt;:8080&lt;/code&gt; or &lt;code&gt;http://mybeautifuldomain.com:8080&lt;/code&gt; to view the test page you're running is due to &lt;strong&gt;K3s&lt;/strong&gt; (and &lt;strong&gt;Kubernetes&lt;/strong&gt; implementations in general) not exposing pods by default. And that's the sensible decision, considering that not every application is supposed to be reached from outside, like a &lt;strong&gt;database&lt;/strong&gt; providing storage for your backend service&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Those access restrictions are created with the container runtime using &lt;a href="https://en.wikipedia.org/wiki/Iptables" rel="noopener noreferrer"&gt;&lt;strong&gt;iptables&lt;/strong&gt;&lt;/a&gt; to set network rules. I've learned it the hard way when &lt;strong&gt;K3s&lt;/strong&gt; conflicted with the &lt;a href="https://netfilter.org/projects/nftables/" rel="noopener noreferrer"&gt;&lt;strong&gt;nftables&lt;/strong&gt;&lt;/a&gt; system service I had already running (fixed by disabling the latter). Time to update to new tools, &lt;strong&gt;containerd&lt;/strong&gt;!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Moreover, while it's obvious where external requests should go to when we have a single application, how to handle many containers exposed to Internet access? For instance, it's not practical or sometimes even possible to demand an explicit port to select different Web services (e.g. &lt;code&gt;mydomain.com:8081&lt;/code&gt; and &lt;code&gt;mydomain.com:8082&lt;/code&gt;), being the standard to use the default &lt;code&gt;80&lt;/code&gt; and &lt;code&gt;443&lt;/code&gt; ports for &lt;strong&gt;HTTP&lt;/strong&gt; and &lt;strong&gt;HTTPS&lt;/strong&gt;, respectively&lt;/p&gt;

&lt;p&gt;With such requirement, it's common to share a single IP address (&lt;strong&gt;host&lt;/strong&gt; and &lt;strong&gt;port&lt;/strong&gt;) using &lt;strong&gt;paths&lt;/strong&gt; (e.g. &lt;code&gt;mydomain.com/app1&lt;/code&gt; and &lt;code&gt;mydomain.com/app2&lt;/code&gt;) or &lt;strong&gt;subdomains&lt;/strong&gt; (e.g. &lt;code&gt;app1.mydomain.com&lt;/code&gt; and &lt;code&gt;app2.mydomain.com&lt;/code&gt;) to address a particular process. To achieve that, messages have to be interpreted for rerouting [to a local port] before reaching their intended target, a task performed by what is known as a &lt;a href="https://www.cloudflare.com/learning/cdn/glossary/reverse-proxy/" rel="noopener noreferrer"&gt;&lt;strong&gt;reverse proxy&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In &lt;strong&gt;K8s&lt;/strong&gt; terminology, the component responsible for that functionality is called &lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" rel="noopener noreferrer"&gt;&lt;strong&gt;Ingress&lt;/strong&gt;&lt;/a&gt;. It's actually a common interface for different implementations named &lt;strong&gt;Ingress Controllers&lt;/strong&gt;, of which it's &lt;strong&gt;usually&lt;/strong&gt; recommended (if you're not doing anything fancy or advanced) to use the &lt;a href="https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/" rel="noopener noreferrer"&gt;&lt;strong&gt;NGINX&lt;/strong&gt;&lt;/a&gt;-based one, as it's &lt;a href="https://github.com/kubernetes/ingress-nginx" rel="noopener noreferrer"&gt;officially maintained&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I emphasize &lt;strong&gt;usually&lt;/strong&gt; because &lt;strong&gt;K3s&lt;/strong&gt; is different and comes with a &lt;a href="https://traefik.io/traefik/" rel="noopener noreferrer"&gt;&lt;strong&gt;Traefik&lt;/strong&gt;&lt;/a&gt;-based ingress controller by default. Taking that into account, as much as I like &lt;strong&gt;NGINX&lt;/strong&gt; outside the container's world, I'd rather keep things simple and use what's already in place&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's totally fine to use &lt;strong&gt;Ingress-Nginx&lt;/strong&gt;, tough. Just get into the &lt;a href="https://github.com/kubernetes/ingress-nginx" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub&lt;/strong&gt; repository&lt;/a&gt; and follow the instructions. I've used it myself before running into problems with upgrades (but you're not that dumb, right?). Be aware that all components will be created in their own predefined namespace, valid system-wide&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now we may use the provided controller to set our &lt;strong&gt;ingress&lt;/strong&gt; rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;                    
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;                                     &lt;span class="c1"&gt;# Component type&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;                                     &lt;span class="c1"&gt;# Component name&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;                                 &lt;span class="c1"&gt;# You may add the default namespace for components as a paramenter&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;loadBalancer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;                       &lt;span class="c1"&gt;# Type of controller being used    &lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                          &lt;span class="c1"&gt;# Routing rules&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppaserver.dynv6.net&lt;/span&gt;                  &lt;span class="c1"&gt;# Expected domain name of request, including subdomain&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                         &lt;span class="c1"&gt;# For HTTP or HTTPS requests (standard ports)&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                      &lt;span class="c1"&gt;# Behavior for different base paths&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;                                 &lt;span class="c1"&gt;# For all request paths&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-service&lt;/span&gt;               &lt;span class="c1"&gt;# Redirect to this service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;                      &lt;span class="c1"&gt;# Redirect to this internal service port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;As now the ingress exposes the service to the external network, it's not required to set the configuration &lt;code&gt;type: LoadBalancer&lt;/code&gt; for &lt;code&gt;welcome-service&lt;/code&gt;, as done &lt;a href="https://dev.to/ancapepe/the-home-server-journey-2-the-control-room-fng"&gt;on the previous chapter&lt;/a&gt;, which changes its behavior to the default &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/#type-clusterip" rel="noopener noreferrer"&gt;&lt;strong&gt;ClusterIP&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Save it to a file, apply the manifest with &lt;code&gt;kubectl&lt;/code&gt;, and in the case everything is correct (including the network instructions from the first article), the test page should be accessible via domain name &lt;strong&gt;OR&lt;/strong&gt; you get something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0k7y0qiij5y1mt2grlt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0k7y0qiij5y1mt2grlt.png" alt="Image description" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which is just the browser being picky with non-secure access, using &lt;code&gt;http://&lt;/code&gt; instead of &lt;code&gt;https://&lt;/code&gt; or not having valid certificates (more on that later). If you want to check the contents of the page, just be a pro for now and use &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="s2"&gt;"http://choppaserver.dynv6.net"&lt;/span&gt;                                                                                                                                                                
&amp;lt;&lt;span class="o"&gt;!&lt;/span&gt;DOCTYPE HTML&amp;gt;
&amp;lt;html &lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&amp;lt;&lt;span class="nb"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&amp;lt;meta &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"utf-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&amp;lt;title&amp;gt;Directory listing &lt;span class="k"&gt;for&lt;/span&gt; /&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Directory listing &lt;span class="k"&gt;for&lt;/span&gt; /&amp;lt;/h1&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"welcome.txt"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;welcome.txt&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="s2"&gt;"http://choppaserver.dynv6.net/welcome.txt"&lt;/span&gt;                                                                                                                                                     
Hello, world!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Almost the same... Who needs browsers anyway?)&lt;/p&gt;

&lt;h3&gt;
  
  
  Papers, please
&lt;/h3&gt;

&lt;p&gt;Seriously, though, &lt;a href="https://www.cloudflare.com/learning/ssl/what-is-an-ssl-certificate/" rel="noopener noreferrer"&gt;&lt;strong&gt;SSL certificates&lt;/strong&gt;&lt;/a&gt; are important nowadays [with &lt;strong&gt;HTTPS&lt;/strong&gt; being ubiquitous], and we need to get one. The easiest way is to let them be automatically generated by &lt;a href="https://cert-manager.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;cert-manager&lt;/strong&gt;&lt;/a&gt;. To cut it short, &lt;a href="https://github.com/cert-manager/cert-manager/releases" rel="noopener noreferrer"&gt;get the latest version of the manifest file from &lt;strong&gt;GitHub&lt;/strong&gt;&lt;/a&gt; or simply install directly from the download link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/jetstack/cert-manager/releases/download/&amp;lt;desired version&amp;gt;/cert-manager.yaml
namespace/cert-manager created
customresourcedefinition.apiextensions.k8s.io/certificaterequests.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/certificates.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/challenges.acme.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/clusterissuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/issuers.cert-manager.io created
customresourcedefinition.apiextensions.k8s.io/orders.acme.cert-manager.io created
serviceaccount/cert-manager-cainjector created
serviceaccount/cert-manager created
serviceaccount/cert-manager-webhook created
clusterrole.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
clusterrole.rbac.authorization.k8s.io/cert-manager-cluster-view created
clusterrole.rbac.authorization.k8s.io/cert-manager-view created
clusterrole.rbac.authorization.k8s.io/cert-manager-edit created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-approve:cert-manager-io created
clusterrole.rbac.authorization.k8s.io/cert-manager-controller-certificatesigningrequests created
clusterrole.rbac.authorization.k8s.io/cert-manager-webhook:subjectaccessreviews created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-cainjector created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-issuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-clusterissuers created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificates created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-orders created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-challenges created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-ingress-shim created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-approve:cert-manager-io created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-controller-certificatesigningrequests created
clusterrolebinding.rbac.authorization.k8s.io/cert-manager-webhook:subjectaccessreviews created
role.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
role.rbac.authorization.k8s.io/cert-manager:leaderelection created
role.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving created
rolebinding.rbac.authorization.k8s.io/cert-manager-cainjector:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager:leaderelection created
rolebinding.rbac.authorization.k8s.io/cert-manager-webhook:dynamic-serving created
service/cert-manager created
service/cert-manager-webhook created
deployment.apps/cert-manager-cainjector created
deployment.apps/cert-manager created
deployment.apps/cert-manager-webhook created
mutatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
validatingwebhookconfiguration.admissionregistration.k8s.io/cert-manager-webhook created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Quite a few pieces, huh?)&lt;/p&gt;

&lt;p&gt;Wait for the manager pods to complete their initialization:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhbz4tfuigh23q3vsch9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flhbz4tfuigh23q3vsch9.png" alt="Cert-manager pods" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The manifest not only creates many standard &lt;strong&gt;K8s&lt;/strong&gt; resources but also defines new custom ones, like the &lt;strong&gt;ClusterIssuer&lt;/strong&gt; we have to manually add now for each environment (only one in our case):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager.io/v1&lt;/span&gt;      &lt;span class="c1"&gt;# API service created by cert-manager&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIssuer&lt;/span&gt;                 &lt;span class="c1"&gt;# Custom component type&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;letsencrypt&lt;/span&gt;
 &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
 &lt;span class="na"&gt;acme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="c1"&gt;# The ACME server URL&lt;/span&gt;
   &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://acme-v02.api.letsencrypt.org/directory&lt;/span&gt;
   &lt;span class="c1"&gt;# Email address used for ACME registration&lt;/span&gt;
   &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;your e-mail here&amp;gt;&lt;/span&gt;
   &lt;span class="c1"&gt;# Name of a secret used to store the ACME account private key&lt;/span&gt;
   &lt;span class="na"&gt;privateKeySecretRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;letsencrypt&lt;/span&gt;
   &lt;span class="c1"&gt;# Enable the HTTP-01 challenge provider&lt;/span&gt;
   &lt;span class="na"&gt;solvers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http01&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;traefik&lt;/span&gt;            &lt;span class="c1"&gt;# Ingress controller type&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(As always, save the &lt;strong&gt;.yaml&lt;/strong&gt; and apply. A &lt;strong&gt;ClusterIssuer&lt;/strong&gt; is not namespace-scoped and can be used by &lt;strong&gt;Certificate&lt;/strong&gt; resources in any namespace)&lt;/p&gt;

&lt;p&gt;Here we're using the &lt;strong&gt;ACME&lt;/strong&gt; challenge with an &lt;strong&gt;HTTP01&lt;/strong&gt; solver to get a valid result from the &lt;a href="https://letsencrypt.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Let's Encrypt&lt;/strong&gt;&lt;/a&gt; certificate authority, and I honestly can't explain to you properly what all that means. However, one thing that I know is that this particular solver requires the ability to open port &lt;code&gt;80&lt;/code&gt; to receive requests. It's common for &lt;a href="https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers" rel="noopener noreferrer"&gt;&lt;strong&gt;system ports&lt;/strong&gt;&lt;/a&gt; (below &lt;code&gt;1024&lt;/code&gt;) to be restricted by your &lt;strong&gt;ISP&lt;/strong&gt;, and if that's your case a &lt;strong&gt;DNS01&lt;/strong&gt; solver might be useful. Please consult &lt;a href="https://cert-manager.io/docs/configuration/acme/" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt; for more&lt;/p&gt;

&lt;p&gt;Finally, we can edit our ingress manifest to define the domains requiring a certificate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;                    
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;                                     &lt;span class="c1"&gt;# Component type&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;                                     &lt;span class="c1"&gt;# Component name&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;                                 &lt;span class="c1"&gt;# You may add the default namespace for components as a paramenter&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;cert-manager.io/cluster-issuer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;letsencrypt&lt;/span&gt;   &lt;span class="c1"&gt;# Reference the utilized ClusterIssuer name &lt;/span&gt;
    &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;          &lt;span class="c1"&gt;# Ingress controller type (yes, again)&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;loadBalancer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;traefik&lt;/span&gt;                       &lt;span class="c1"&gt;# Ingress controller type&lt;/span&gt;
  &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                            &lt;span class="c1"&gt;# Certificate options&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                        &lt;span class="c1"&gt;# List of valid hosts&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;choppaserver.dynv6.net&lt;/span&gt;
    &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;certificate&lt;/span&gt;                       &lt;span class="c1"&gt;# Secret component that will store the certificates      &lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                          &lt;span class="c1"&gt;# Routing rules&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;choppaserver.dynv6.net&lt;/span&gt;                  &lt;span class="c1"&gt;# Expected domain name of request, including subdomain&lt;/span&gt;
    &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                         &lt;span class="c1"&gt;# For HTTP or HTTPS requests&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                      &lt;span class="c1"&gt;# Behavior for different base paths&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;                                 &lt;span class="c1"&gt;# For all request paths&lt;/span&gt;
          &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
          &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-service&lt;/span&gt;               &lt;span class="c1"&gt;# Redirect to this service&lt;/span&gt;
              &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;                      &lt;span class="c1"&gt;# Redirect to this internal service port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a while, a secret with the name defined in &lt;code&gt;secretName&lt;/code&gt; should appear in the namespace you're using:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsgkxo97p4yxlpgwkhfn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsgkxo97p4yxlpgwkhfn9.png" alt="Certificate secret" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the browser should stop complaining about the lack of security in your Web page:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0r15yi1f7m4dqwke9gg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc0r15yi1f7m4dqwke9gg.png" alt="Secure access" width="694" height="492"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congrats! Now you the actual world can hear you say "hello". For more information on setting up &lt;strong&gt;cert-manager&lt;/strong&gt;, see &lt;a href="https://levelup.gitconnected.com/easy-steps-to-install-k3s-with-ssl-certificate-by-traefik-cert-manager-and-lets-encrypt-d74947fe7a8" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; (or &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-an-nginx-ingress-with-cert-manager-on-digitalocean-kubernetes" rel="noopener noreferrer"&gt;this one&lt;/a&gt; for &lt;strong&gt;Ingress-Nginx&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;See you next time&lt;/p&gt;

</description>
      <category>selfhosting</category>
      <category>kubernetes</category>
      <category>censorship</category>
      <category>decentralization</category>
    </item>
    <item>
      <title>The Home Server Journey - 2: The Control Room</title>
      <dc:creator>Beppe</dc:creator>
      <pubDate>Tue, 17 Sep 2024 00:47:49 +0000</pubDate>
      <link>https://dev.to/beppe90/the-home-server-journey-2-the-control-room-fng</link>
      <guid>https://dev.to/beppe90/the-home-server-journey-2-the-control-room-fng</guid>
      <description>&lt;p&gt;I confess that not adding some application deployment to &lt;a href="https://dev.to/ancapepe/the-home-server-journey-1-motivation-and-approach-5fhj"&gt;our first article&lt;/a&gt; left me a bit frustrated, but I was unsure about it's length, even more so for an author whose reputation alone wouldn't [yet] make people pay more attention that what's natural. But let's fix that right away and get something running. &lt;/p&gt;

&lt;h3&gt;
  
  
  Hello, World!
&lt;/h3&gt;

&lt;p&gt;As we're talking about &lt;strong&gt;Internet&lt;/strong&gt; servers, what better than a &lt;strong&gt;Web&lt;/strong&gt; page? I've found a very fitting test container in &lt;strong&gt;jdkelley&lt;/strong&gt;'s &lt;a href="https://hub.docker.com/r/jdkelley/simple-http-server" rel="noopener noreferrer"&gt;&lt;code&gt;simple-http-server&lt;/code&gt;&lt;/a&gt;, but there was an issue: no image available for the &lt;strong&gt;ARM&lt;/strong&gt; architecture. That's the only reason why I had to build one and publish it to &lt;a href="https://hub.docker.com/repository/docker/ancapepe/http-server/general" rel="noopener noreferrer"&gt;my own repository&lt;/a&gt;, I swear&lt;/p&gt;

&lt;p&gt;If those concepts related to containers are still confusing to you, I recommend following &lt;strong&gt;Nana Janashia&lt;/strong&gt;'s tutorials and crash courses. She's an amazing teacher and I wouldn't be able to quickly summarize all the knowledge she provides. Her &lt;strong&gt;Docker&lt;/strong&gt; lectures compilation is linked below:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/3c-iBn73dDE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;With that out of the way (thanks, Nana), let's use our test image in our first &lt;strong&gt;Kubernetes&lt;/strong&gt; &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;&lt;strong&gt;deployment&lt;/strong&gt;&lt;/a&gt;, where containers are encapsulated as &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/" rel="noopener noreferrer"&gt;&lt;strong&gt;pods&lt;/strong&gt;&lt;/a&gt;. From now on I'll be abusing comments inside the &lt;a href="https://en.wikipedia.org/wiki/YAML" rel="noopener noreferrer"&gt;&lt;strong&gt;YAML&lt;/strong&gt;&lt;/a&gt;-format &lt;strong&gt;manifest files&lt;/strong&gt; as it's an easier and more compact way to describe each parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;                        &lt;span class="c1"&gt;# Specification version&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;                       &lt;span class="c1"&gt;# Static/immutable data/information component&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                             &lt;span class="c1"&gt;# Identification attributes&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-config&lt;/span&gt;                  &lt;span class="c1"&gt;# Component name&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                 &lt;span class="c1"&gt;# Contents list&lt;/span&gt;
  &lt;span class="na"&gt;welcome.txt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;                        &lt;span class="c1"&gt;# Data item name and contents&lt;/span&gt;
    &lt;span class="s"&gt;Hello, world!&lt;/span&gt;
&lt;span class="s"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;                      &lt;span class="c1"&gt;# Immutable application management component&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-deploy&lt;/span&gt;                &lt;span class="c1"&gt;# Component name&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                             &lt;span class="c1"&gt;# Markers used for identification by other components&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome&lt;/span&gt;                                      
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;                         &lt;span class="c1"&gt;# Number of application pod instances&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                        &lt;span class="c1"&gt;# Markers searched for in other components&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome&lt;/span&gt;                                    
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                           &lt;span class="c1"&gt;# Settings for every pod that is part of this deployment&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                         &lt;span class="c1"&gt;# List of deployed container in each pod replica&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome&lt;/span&gt;                                 &lt;span class="c1"&gt;# Container name&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ancapepe/http-server:python-latest&lt;/span&gt;     &lt;span class="c1"&gt;# Image used for this container&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                        &lt;span class="c1"&gt;# List of exposed network ports for this container&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8000&lt;/span&gt;                           &lt;span class="c1"&gt;# Port number&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-http&lt;/span&gt;                            &lt;span class="c1"&gt;# Optional port label for reference &lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                 &lt;span class="c1"&gt;# Data storages accessible to this container&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-volume&lt;/span&gt;                        &lt;span class="c1"&gt;# Storage name&lt;/span&gt;
            &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/serve/welcome.txt&lt;/span&gt;               &lt;span class="c1"&gt;# Storage path inside this container&lt;/span&gt;
            &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome.txt&lt;/span&gt;                        &lt;span class="c1"&gt;# Storage path inside the original volume&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                            &lt;span class="c1"&gt;# List of volumes available for mounting&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-volume&lt;/span&gt;                &lt;span class="c1"&gt;# Volume name&lt;/span&gt;
        &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                          &lt;span class="c1"&gt;# Use a ConfigMap component as data source&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-config&lt;/span&gt;              &lt;span class="c1"&gt;# ConfigMap reference name&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;                         &lt;span class="c1"&gt;# Internal network access component&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-service&lt;/span&gt;                 &lt;span class="c1"&gt;# Component name&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;                    &lt;span class="c1"&gt;# Expose service to non-K8s processes&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                             &lt;span class="c1"&gt;# Bind to deployments with those labels&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                                &lt;span class="c1"&gt;# List of exposed ports&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;                       &lt;span class="c1"&gt;# Use TCP Internet protocol&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;                          &lt;span class="c1"&gt;# Listen on port 8080&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;welcome-http&lt;/span&gt;            &lt;span class="c1"&gt;# Redirect trafic to this container port&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Copy it to a text editor and save it to something like &lt;code&gt;welcome.yaml&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Among other things, &lt;strong&gt;Kubernetes&lt;/strong&gt; are a solution for &lt;a href="https://www.nops.io/blog/horizontal-vs-vertical-scaling" rel="noopener noreferrer"&gt;&lt;strong&gt;horizontal scaling&lt;/strong&gt;&lt;/a&gt;: if your application process is bogged down by many requests, you may instantiate extra copies of it (&lt;strong&gt;replicas&lt;/strong&gt;) in order to server a larger demand, even dynamically, but here we'll work with a prefixed amount. Moreover, see how &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/" rel="noopener noreferrer"&gt;&lt;strong&gt;services&lt;/strong&gt;&lt;/a&gt; not only centralize access to all pods of a deployment, but allow us to use a different connection port than the one defined in the &lt;strong&gt;Docker&lt;/strong&gt; image &lt;/p&gt;

&lt;p&gt;A concept you should have in mind when working with &lt;strong&gt;K8s&lt;/strong&gt; is &lt;a href="https://simple.wikipedia.org/wiki/Idempotence" rel="noopener noreferrer"&gt;&lt;strong&gt;idempotence&lt;/strong&gt;&lt;/a&gt;: manifests such as above don't describe a sequence of operations to set up your pods and auxiliary components, but the &lt;strong&gt;desired final state&lt;/strong&gt;, from which the operations (to either create or restore the deployment) are automatically defined. That way, trying to re-apply an already [successfully] applied configuration will result in no changes to the cluster&lt;/p&gt;

&lt;p&gt;The time has come to get our hands dirty. Surely you may submit your &lt;strong&gt;YAML&lt;/strong&gt; files by copying each new version to one of your nodes and invoking &lt;code&gt;k3s kubectl&lt;/code&gt; via &lt;strong&gt;SSH&lt;/strong&gt;, but how about doing that from the comfort of your desktop, where you have originally edited and saved the manifests?&lt;/p&gt;

&lt;p&gt;It is possible to install &lt;strong&gt;kubectl&lt;/strong&gt; independently of the &lt;strong&gt;K8s&lt;/strong&gt; cluster (follow &lt;a href="https://kubernetes.io/docs/tasks/tools/#kubectl" rel="noopener noreferrer"&gt;the instructions for each platform&lt;/a&gt;), but it won't work out-of-the-box, as your local client doesn't know how to find the remote cluster yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get nodes                                                                                                                                                                                  
E0916 18:45:31.872817   11709 memcache.go:265] couldn&lt;span class="s1"&gt;'t get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E0916 18:45:31.873268   11709 memcache.go:265] couldn'&lt;/span&gt;t get current server API group list: Get &lt;span class="s2"&gt;"http://localhost:8080/api?timeout=32s"&lt;/span&gt;: dial tcp &lt;span class="o"&gt;[&lt;/span&gt;::1]:8080: connect: connection refused
E0916 18:45:31.875167   11709 memcache.go:265] couldn&lt;span class="s1"&gt;'t get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
E0916 18:45:31.875837   11709 memcache.go:265] couldn'&lt;/span&gt;t get current server API group list: Get &lt;span class="s2"&gt;"http://localhost:8080/api?timeout=32s"&lt;/span&gt;: dial tcp &lt;span class="o"&gt;[&lt;/span&gt;::1]:8080: connect: connection refused
E0916 18:45:31.877524   11709 memcache.go:265] couldn&lt;span class="s1"&gt;'t get current server API group list: Get "http://localhost:8080/api?timeout=32s": dial tcp [::1]:8080: connect: connection refused
The connection to the server localhost:8080 was refused - did you specify the right host or port?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to get that information, log into one of your cluster machines and get the system configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;k3s kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://127.0.0.1:6443
  name: default
contexts:
- context:
    cluster: default
    user: default
  name: default
current-context: default
kind: Config
preferences: &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="nb"&gt;users&lt;/span&gt;:
- name: default
  user:
    client-certificate-data: DATA+OMITTED
    client-key-data: DATA+OMITTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Here authentication data is omitted for safety reasons. in order to show the complete configuration, add the &lt;code&gt;--raw&lt;/code&gt; option to the end of the command)&lt;/p&gt;

&lt;p&gt;Copy the &lt;strong&gt;raw&lt;/strong&gt; output of the command, change the &lt;code&gt;server&lt;/code&gt; IP (just the &lt;code&gt;127.0.0.1&lt;/code&gt;) to match your master node's one, and overwrite your desktop's &lt;strong&gt;kubeconfig&lt;/strong&gt; file (on Linux, the default location is &lt;code&gt;&amp;lt;user home directory&amp;gt;/.kube/confg&lt;/code&gt;) with these modified contents. Now you should be able to use the control client successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get nodes                                                                                                                                                                                
NAME              STATUS     ROLES                  AGE   VERSION
odroidn2-master   Ready      control-plane,master   9d    v1.30.3+k3s1
rpi4-agent        Ready      &amp;lt;none&amp;gt;                 9d    v1.30.3+k3s1

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, apply our test deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; welcome.yaml
configmap/welcome-config created
deployment.apps/welcome-deploy created
service/welcome-service created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods                                                                                                                                                        
NAME                             READY   STATUS    RESTARTS   AGE
welcome-deploy-5bd4cb78b-hvj9z   1/1     Running   0          75s
welcome-deploy-5bd4cb78b-xb99p   1/1     Running   0          75s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you regret your decision (already?), that operation may be reverted using the same manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; welcome.yaml                                                                                                                                           
configmap &lt;span class="s2"&gt;"welcome-config"&lt;/span&gt; deleted
deployment.apps &lt;span class="s2"&gt;"welcome-deploy"&lt;/span&gt; deleted
service &lt;span class="s2"&gt;"welcome-service"&lt;/span&gt; deleted
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods                                                                                                                                                        
NAME                             READY   STATUS        RESTARTS   AGE
welcome-deploy-5bd4cb78b-hvj9z   1/1     Terminating   0          114s
welcome-deploy-5bd4cb78b-xb99p   1/1     Terminating   0          114s
&lt;span class="c"&gt;# A little while later&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get pods 
No resources found &lt;span class="k"&gt;in &lt;/span&gt;default namespace.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, pods of a given deployment get a random suffix attached to their names, in order to differentiate them. Also, all &lt;strong&gt;Kubernetes&lt;/strong&gt; are organized in &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" rel="noopener noreferrer"&gt;&lt;strong&gt;namespaces&lt;/strong&gt;&lt;/a&gt; to help with resource management. If not informed as a &lt;code&gt;kubectl&lt;/code&gt; command option or inside the file itself, the &lt;code&gt;default&lt;/code&gt; namespace is used&lt;/p&gt;

&lt;p&gt;In order to perform the same operation with a particular namespace, create it first (if not created already):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl create namespace &lt;span class="nb"&gt;test                                                                                                                                          
&lt;/span&gt;namespace/test created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; welcome.yaml &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test                                                                                                                           
&lt;/span&gt;configmap/welcome-config created
deployment.apps/welcome-deploy created
service/welcome-service created
&lt;span class="nv"&gt;$ &lt;/span&gt;kubectl get all &lt;span class="nt"&gt;--namespace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;test                                                                                                                                         
&lt;/span&gt;NAME                                 READY   STATUS    RESTARTS   AGE
pod/welcome-deploy-5bd4cb78b-5df7d   1/1     Running   0          16s
pod/welcome-deploy-5bd4cb78b-9cpdj   1/1     Running   0          16s

NAME                      TYPE           CLUSTER-IP      EXTERNAL-IP                 PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;          AGE
service/welcome-service   LoadBalancer   10.43.194.209   192.168.3.10,192.168.3.11   8080:31238/TCP   16s

NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/welcome-deploy   2/2     2            2           16s

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/welcome-deploy-5bd4cb78b   2         2         2       16s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(See how namespaces allow to easily select all components that we wish to monitor)&lt;/p&gt;

&lt;p&gt;With a successful deployment, our Web page should be displayed by accessing one of the external IPs from the &lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer" rel="noopener noreferrer"&gt;&lt;strong&gt;LoadBalancer&lt;/strong&gt;&lt;/a&gt; service (which are the node IPs) on any browser (generally you have to make the usage of &lt;strong&gt;HTTP&lt;/strong&gt; over &lt;strong&gt;HTTPS&lt;/strong&gt; explicitly):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhafgte4bzmsn1zb6x84c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhafgte4bzmsn1zb6x84c.png" alt="Accessing Web application from browser" width="785" height="889"&gt;&lt;/a&gt;&lt;br&gt;
(Opening the listed file works too!)&lt;/p&gt;

&lt;p&gt;Congrats! Go add it to your list of &lt;strong&gt;LinkedIn&lt;/strong&gt;'s skills (just kidding)&lt;/p&gt;

&lt;p&gt;Again, if you're interested in understanding all those concepts in more depth, in order to follow along this and the next chapters of our guide, Nana comes to the rescue again with her &lt;strong&gt;Kubernetes&lt;/strong&gt; tutorial:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/X48VuDVv0do"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  A nicer view
&lt;/h3&gt;

&lt;p&gt;That's all well and good, but I bet you can imagine how, as you're developing and running more and more components, issuing commands and keeping track of stuff using &lt;code&gt;kubectl&lt;/code&gt; gets quite repetitive. One may open multiple terminals running the &lt;code&gt;get&lt;/code&gt; command with &lt;code&gt;--watch&lt;/code&gt; as option for automatic updates, but switching between windows is not that great either&lt;/p&gt;

&lt;p&gt;Thankfully, for whoever likes that approach, there are friendlier &lt;a href="https://en.wikipedia.org/wiki/Graphical_user_interface" rel="noopener noreferrer"&gt;&lt;strong&gt;GUI&lt;/strong&gt;&lt;/a&gt;s to help manage yours clusters and deployments. &lt;a href="https://ranchermanager.docs.rancher.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;Rancher&lt;/strong&gt;&lt;/a&gt;, developed by the same folks from &lt;strong&gt;K3s&lt;/strong&gt;, is one of them, but honestly I've found too complicated to set up. In my previous job I came across an alternative that I consider much more practical: &lt;a href="https://k8slens.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Lens&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After installing it, if you're interested, there's not much to do to get it running for your cluster. Select the option to add a new one from a &lt;strong&gt;kubeconfig&lt;/strong&gt; and paste the contents from your local &lt;strong&gt;kubectl&lt;/strong&gt; configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd11l4t19tu3fedcmbotn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd11l4t19tu3fedcmbotn.png" alt="Lens cluster configuration" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If everything is correct, you'll be able to access it, visualize your resources, edit manifest files, and even use the integrate terminal to create new components with &lt;code&gt;kubectl&lt;/code&gt; (There doesn't seem to be a widget for creation, only modification or deletion of existing components):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5eu8ejyfgrptvhnqx38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5eu8ejyfgrptvhnqx38.png" alt="Image description" width="800" height="428"&gt;&lt;/a&gt;&lt;br&gt;
(I don't know about you, but that's what I'll be using from now on)&lt;/p&gt;

&lt;p&gt;That's it for now! Thanks for reading and let's start making some real-world stuff next time&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/ancapepe/the-home-server-journey-3-an-actually-global-hello-2af6"&gt;&lt;strong&gt;Chapter 3&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>censorship</category>
      <category>decentralization</category>
      <category>selfhosting</category>
    </item>
    <item>
      <title>The Home Server Journey - 1: Motivation and Approach</title>
      <dc:creator>Beppe</dc:creator>
      <pubDate>Sun, 15 Sep 2024 12:22:52 +0000</pubDate>
      <link>https://dev.to/beppe90/the-home-server-journey-1-motivation-and-approach-5fhj</link>
      <guid>https://dev.to/beppe90/the-home-server-journey-1-motivation-and-approach-5fhj</guid>
      <description>&lt;p&gt;Inspired by the recent &lt;strong&gt;Twitter&lt;/strong&gt;/&lt;strong&gt;X&lt;/strong&gt; situation here in Brazil (no strong opinions on Elon Musk and all this drama, but it surely sucks to lose access to an useful service) and by &lt;a href="https://lettersfrommercia.substack.com/p/time-to-take-privacy-seriously" rel="noopener noreferrer"&gt;an important article by Ceadda of Mercia&lt;/a&gt;, I've decided to set an old project of mine in motion.&lt;/p&gt;

&lt;p&gt;Truth is, we grow so used to many readily accessible (even if not completely free) platforms on the Internet that we end up taking this ability to communicate and inform ourselves for granted. We easily forget how liberty has to be conquered every day, and that has to be backed by power i.e. the capacity to do what we want to do&lt;/p&gt;

&lt;p&gt;Anyway, there are plenty of speeches and commentary around that would do a much better job than me in convincing you of that reality, not to mention the risks of providing all your data directly to third-parties. So here I intend to concentrate where I can add something meaningful: give you some ideas and guidance on how you may own your Internet life a bit more&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;Living in reality is knowing where things come from and where they go to&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Social media, messaging, videos, photos, e-mail, blogs like this one and every other thing that we access on the Internet are all hosted out there, on &lt;strong&gt;&lt;em&gt;The Cloud&lt;/em&gt;&lt;/strong&gt;. However, as the saying goes, &lt;strong&gt;&lt;em&gt;"'The Cloud' is just someone else's computer"&lt;/em&gt;&lt;/strong&gt;. And why couldn't it be YOUR computer? Well, it could, if you're willing to make some sacrifices  &lt;/p&gt;

&lt;p&gt;You won't be able to run e.g. a personal &lt;strong&gt;Facebook&lt;/strong&gt; or &lt;strong&gt;Instagram&lt;/strong&gt; server from home, but if you care more about the &lt;em&gt;functionality&lt;/em&gt; they provide, well, you may be able to actually own something close to it. Of course your friends would have to come over to your place, and many probably won't. That's part of what I meant by "make some sacrifices": your personalized Web presence will be a bit lonely until it gets some traction&lt;/p&gt;

&lt;p&gt;But, let's be real, if you're looking into building homeservers having almost no friends is probably the case already... I say it from experience&lt;/p&gt;

&lt;p&gt;Ok, ok, maybe you DO have quite some friends [on the Internet] and don't want to give up interacting with them. You'd still find usefulness in having your own "cloud" storage/backup, or a less censorable place to publish your ideas &lt;/p&gt;

&lt;p&gt;I'll try to show a bit of everything. Pick whatever suits you &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;em&gt;The meat and potatoes of the issue&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;I don't plan on being condescending to the point of assuming you don't know the distinction between hardware as software. You might have already figured out that those personal software services would need dedicated hardware to be run, and it would be a good thing to have a relatively low-cost solution, so that affording a brand new &lt;strong&gt;Intel x86&lt;/strong&gt; PC (calm down, &lt;strong&gt;Apple M1&lt;/strong&gt; users) to keep turned on 24/7 is not a requirement&lt;/p&gt;

&lt;p&gt;A common approach that I have adopted is resorting to &lt;strong&gt;ARM&lt;/strong&gt;-based single board computers. They are cheaper, consume less power and take less space inside my modest house. Below I display my humble initial setup of an &lt;strong&gt;Hardkernel Odroid N2+&lt;/strong&gt; and a well-know &lt;strong&gt;Raspberry Pi 4&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs5re7cpdkqy3pyy9wl9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs5re7cpdkqy3pyy9wl9x.png" alt="Odroid N2+ and Raspberry Pi 4 in a home cluster" width="800" height="522"&gt;&lt;/a&gt;&lt;br&gt;
(Size doesn't matter if you know how to use it, or so they say)&lt;/p&gt;

&lt;p&gt;I have also attached one spare hard drive to each one of them using external &lt;strong&gt;USB 3.0&lt;/strong&gt; to &lt;strong&gt;SATA&lt;/strong&gt; adapter cases, so that we have larger and more reliable storage. Besides, as you might have noticed, there is no screen, keyboard or mouse there, because I'll be managing everything software-wise from my desktop, over the local network (&lt;a href="https://www.cisco.com/c/en/us/products/switches/what-is-a-lan-local-area-network.html" rel="noopener noreferrer"&gt;&lt;strong&gt;LAN&lt;/strong&gt;&lt;/a&gt;)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please make sure you have the correct/recommended power supply connected to each board, specially when using external devices powered through USB. I've just had some &lt;strong&gt;undervoltage&lt;/strong&gt; issues with the Raspberri Pi for not using the ideal one&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Both machines are running &lt;a href="https://manjaro.org/products/download/arm" rel="noopener noreferrer"&gt;the minimal &lt;strong&gt;ARM&lt;/strong&gt; version of &lt;strong&gt;Manjaro Linux&lt;/strong&gt;&lt;/a&gt;. As a long-time user of &lt;strong&gt;Manjaro&lt;/strong&gt; on my PC, I found it easier to let &lt;a href="https://gitlab.manjaro.org/manjaro-arm/applications/manjaro-arm-installer" rel="noopener noreferrer"&gt;&lt;strong&gt;manjaro-arm-installer&lt;/strong&gt;&lt;/a&gt; choose the right configuration for each board, download the image and write it to its respective SD card. There are plenty of options for operating systems, with it's own installation instructions, but generally you'll be taking and &lt;strong&gt;.img&lt;/strong&gt; file and flashing it to the card with some tool like &lt;a href="https://etcher.balena.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Etcher&lt;/strong&gt;&lt;/a&gt; or &lt;strong&gt;dd&lt;/strong&gt;. Take a look at what you like the most (preferably some &lt;strong&gt;Linux &lt;a href="https://en.wikipedia.org/wiki/Command-line_interface" rel="noopener noreferrer"&gt;CLI&lt;/a&gt;&lt;/strong&gt; version) or just use the same I did, you'll have to consult extra documentation regardless, as I can't give you all the details without making this article too long for my taste&lt;/p&gt;

&lt;p&gt;Notes on networking&lt;/p&gt;

&lt;p&gt;Needless to say, your server machines need Internet connection, preferably &lt;strong&gt;Ethernet&lt;/strong&gt;, so that it can be detected without logging into each one locally and configuring Wi-Fi. Another [not so obvious] requirement is that at least one of them (your master) has to be exposed outside of your LAN, so that external devices can reach it (and you can imagine why that's not possible by default). If you have access to your router configuration (I DO hope you have), usually your &lt;a href="https://en.wikipedia.org/wiki/Network_address_translation" rel="noopener noreferrer"&gt;&lt;strong&gt;NAT&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://en.wikipedia.org/wiki/Firewall_(computing)" rel="noopener noreferrer"&gt;&lt;strong&gt;Firewall&lt;/strong&gt;&lt;/a&gt; settings, that's achieved by forwarding the ports used by the services you got running e.g. port &lt;strong&gt;80/TCP&lt;/strong&gt; for &lt;strong&gt;HTTP&lt;/strong&gt; and &lt;strong&gt;443/TCP&lt;/strong&gt; for &lt;strong&gt;HTTPS&lt;/strong&gt; (Web pages):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjc0kbviyaqj73wzo8i2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjc0kbviyaqj73wzo8i2j.png" alt="Port forwarding" width="800" height="706"&gt;&lt;/a&gt;&lt;br&gt;
(If you're using standard &lt;strong&gt;IPv4&lt;/strong&gt;, the machine's host address is valid exclusively inside your LAN, and only the router/gateway has also a global &lt;a href="https://en.wikipedia.org/wiki/Wide_area_network" rel="noopener noreferrer"&gt;&lt;strong&gt;WAN&lt;/strong&gt;&lt;/a&gt; IP. Even with all the &lt;a href="https://en.wikipedia.org/wiki/IPv6" rel="noopener noreferrer"&gt;&lt;strong&gt;IPv6&lt;/strong&gt;&lt;/a&gt; goodies enabled, reachability from outside might be limited for security reasons)&lt;/p&gt;

&lt;p&gt;I'd also recommend reserving static IPs for each one of your server's network interfaces, so that we don't get any unpleasant surprises with changing addresses:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqwmzw77rjjo7ip7te45f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqwmzw77rjjo7ip7te45f.png" alt="Static IPs reservation" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After redirecting incoming traffic from router &lt;strong&gt;WAN&lt;/strong&gt; address to server machine &lt;strong&gt;LAN&lt;/strong&gt; address on the given ports, it's time to make yourself easier to find. Passing your &lt;strong&gt;WAN&lt;/strong&gt; IP around so that people may type it into the browser bar would already work, but that's both hard to remember and unreliable, as addresses attributed to your machine usually change from time to time (lucky you if a static global IP is available).&lt;/p&gt;

&lt;p&gt;Then why not give your virtual place a proper memorable name, like &lt;strong&gt;&lt;em&gt;"mybeautifulsite.com"&lt;/em&gt;&lt;/strong&gt;, that everybody could know? Well, that what name service (&lt;a href="https://en.wikipedia.org/wiki/Domain_Name_System" rel="noopener noreferrer"&gt;&lt;strong&gt;DNS&lt;/strong&gt;&lt;/a&gt;) is for.&lt;/p&gt;

&lt;p&gt;Sadly &lt;strong&gt;domains&lt;/strong&gt; are one aspect of Internet centralization that still cannot be trivially avoided (I know about &lt;a href="https://www.geeksforgeeks.org/top-best-blockchain-dns-software/" rel="noopener noreferrer"&gt;&lt;strong&gt;blockchain DNS&lt;/strong&gt;&lt;/a&gt;, but who supports it?), so you have to rely on a third-party service (e.g. &lt;strong&gt;Cloudflare&lt;/strong&gt;) in order to propagate your &lt;strong&gt;hostname&lt;/strong&gt; across the Web. Generally you have to pay a &lt;a href="https://www.cloudflare.com/learning/dns/glossary/what-is-a-domain-name-registrar/" rel="noopener noreferrer"&gt;&lt;strong&gt;registrar&lt;/strong&gt;&lt;/a&gt; for the right to use a certain name (it's a way to prevent the ambiguity from 2 hosts using the same one), but for testing purposes you may rely on free DNS services (such as &lt;strong&gt;No-IP&lt;/strong&gt; or &lt;strong&gt;Dynv6&lt;/strong&gt;) that provide you a somewhat uglier subdomain alternative:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fna5wzugfv8f5xef3zwb9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fna5wzugfv8f5xef3zwb9.png" alt="DNS zone" width="800" height="272"&gt;&lt;/a&gt;&lt;br&gt;
(They were so kind to tell the entire world my router's IP)&lt;/p&gt;

&lt;p&gt;If you don't manage to get your hands on a static &lt;strong&gt;WAN&lt;/strong&gt; IP, it's important that your &lt;strong&gt;DNS&lt;/strong&gt; service has support for being automatically updated about changes in your dynamic address. Your router itself might be able to notify the &lt;a href="https://www.cloudflare.com/learning/dns/glossary/dynamic-dns/" rel="noopener noreferrer"&gt;&lt;strong&gt;DDNS&lt;/strong&gt;&lt;/a&gt;, but if the proper protocol option is not available you may run a refresh client from one of your computers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyao0l5tji3ypplwdfp0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyao0l5tji3ypplwdfp0k.png" alt="DDNS configuration" width="757" height="1024"&gt;&lt;/a&gt;&lt;br&gt;
(Again, there's too much variation across routers and services for me to cover it here)&lt;/p&gt;

&lt;p&gt;Just don't go trying to reach your server via &lt;strong&gt;DNS&lt;/strong&gt; right after naming it. Propagation takes a while, as many name servers across the world have to be informed. If getting anxious, you may try to check the state of that process using &lt;a href="https://dnschecker.org/" rel="noopener noreferrer"&gt;some Web service&lt;/a&gt; or &lt;strong&gt;nslookup&lt;/strong&gt; command on your &lt;strong&gt;Linux&lt;/strong&gt; system (also available under the same name on &lt;strong&gt;Windows&lt;/strong&gt;, apparently, but who in his/her right mind still uses it?)&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;em&gt;Becoming the helmsman&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;If you've ever tried using a non-mainstream &lt;a href="https://en.wikipedia.org/wiki/Linux_distribution" rel="noopener noreferrer"&gt;&lt;strong&gt;Linux distro&lt;/strong&gt;&lt;/a&gt;, you probably know the pain of looking for a package, not finding it even in a third-party repository, trying to compile it from source and failing miserably over and over (Been there. Done that). &lt;strong&gt;Manjaro&lt;/strong&gt;, as an &lt;a href="https://archlinux.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;Arch&lt;/strong&gt;&lt;/a&gt;-based OS, at least has the very welcome access to the &lt;a href="https://aur.archlinux.org/" rel="noopener noreferrer"&gt;&lt;strong&gt;AUR&lt;/strong&gt;&lt;/a&gt;, a community repository for compilation scripts that automate the process for us mere humans, but not all are regularly maintained and many still break, for a plethora of reasons that you eventually have to figure out and fix in a case-by-case basis&lt;/p&gt;

&lt;p&gt;A way to avoid those headaches would be welcome&lt;/p&gt;

&lt;p&gt;For a long time, I've been the kind of person that sees &lt;strong&gt;Linux&lt;/strong&gt;'s &lt;a href="https://en.wikipedia.org/wiki/Dependency_hell" rel="noopener noreferrer"&gt;Dependency Hell&lt;/a&gt; as a feature rather than a bug: &lt;em&gt;"It's efficient for storage"&lt;/em&gt;, &lt;em&gt;"It forces developers to keep their software updated and in sync with others"&lt;/em&gt;, &lt;em&gt;"Library duplication is r*tarded"&lt;/em&gt;, I've been telling myself. I still find it somewhat true, and don't see &lt;a href="https://en.wikipedia.org/wiki/Sandbox_(computer_security)" rel="noopener noreferrer"&gt;sandboxed&lt;/a&gt; solutions like &lt;strong&gt;Flatpak&lt;/strong&gt;, &lt;strong&gt;Snap&lt;/strong&gt; and &lt;strong&gt;AppImage&lt;/strong&gt; with good eyes, or even &lt;a href="https://realpython.com/python-virtual-environments-a-primer/" rel="noopener noreferrer"&gt;virtual environments&lt;/a&gt; for more than prototyping... Ok, maybe I'm still THAT kind of person, but I've found &lt;a href="https://en.wikipedia.org/wiki/OS-level_virtualization" rel="noopener noreferrer"&gt;&lt;strong&gt;containers&lt;/strong&gt;&lt;/a&gt; to be pretty neat&lt;/p&gt;

&lt;p&gt;Having worked a bit with &lt;a href="https://search.brave.com/search?q=docker&amp;amp;source=desktop" rel="noopener noreferrer"&gt;&lt;strong&gt;Docker&lt;/strong&gt;&lt;/a&gt; in a previous job, I was convinced of how useful this level of isolation is for automated testing and deployment of applications, being able to change system settings and replicate them across countless [physical or virtual] machines. You really start visualizing containers as loosely coupled independent entities exchanging messages, which in turn naturally evolves into the main focus of my approach here: &lt;a href="https://www.vmware.com/topics/container-orchestration" rel="noopener noreferrer"&gt;&lt;strong&gt;orchestration&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And to cut it short, I'd guess in 9 of 10 cases that word would mean using one of the many implementations of &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;&lt;/a&gt; (&lt;strong&gt;K8s&lt;/strong&gt; for short). Not that there aren't other solutions, but I'm not knowledgeable enough to evaluate them properly and I went for what is more popular i.e. with more available documentation. Let me make it clear that this guide is a mish-mash of a number of tutorials and &lt;strong&gt;Stack Overflow&lt;/strong&gt; threads, compiled, updated and adjusted by an amateur who is really stubborn about making all work together. So please bear in mind it might not be the most elegant solution&lt;/p&gt;

&lt;p&gt;For tiny single-board computers, there is a lightweight implementation of &lt;strong&gt;K8s&lt;/strong&gt; called &lt;a href="https://docs.k3s.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;K3s&lt;/strong&gt;&lt;/a&gt;, which has worked well for me. If you're using &lt;strong&gt;Arch&lt;/strong&gt;-based systems (assuming that you have actually gotten the machines set up), keep following my steps for &lt;strong&gt;K3s&lt;/strong&gt; installation. On &lt;strong&gt;Debian&lt;/strong&gt;-based systems like &lt;strong&gt;Ubuntu&lt;/strong&gt; or &lt;strong&gt;Raspbian&lt;/strong&gt;, you'd be probably better off following &lt;strong&gt;NetworkChuck&lt;/strong&gt;'s guide linked below. Otherwise, you'll have to do your own research:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/X9fSMGkjtug"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;As already mentioned, here I have accessed my servers from a desktop, via &lt;a href="https://en.wikipedia.org/wiki/Secure_Shell" rel="noopener noreferrer"&gt;&lt;strong&gt;SSH&lt;/strong&gt;&lt;/a&gt;, and installed &lt;strong&gt;K3s&lt;/strong&gt; using &lt;strong&gt;yay&lt;/strong&gt;, but you may use any other package manager with &lt;strong&gt;AUR&lt;/strong&gt; access like &lt;strong&gt;pikaur&lt;/strong&gt; or &lt;strong&gt;pamac&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh ancapepe@192.168.3.10    
ancapepe@192.168.3.10&lt;span class="s1"&gt;'s password:  
Welcome to Manjaro ARM 
~~Website: https://manjaro.org 
~~Forum:   https://forum.manjaro.org/c/arm 
~~Matrix:  #manjaro-arm-public:matrix.org 
Last login: Wed Sep 11 18:41:48 2024 from 192.168.3.8 
[ancapepe@ChoppaServer-1 ~]$ sudo pacman -S yay 
[sudo] password for ancapepe:  
resolving dependencies...
looking for conflicting packages...

Packages (1) yay-12.3.1-1

Total Download Size:   3.31 MiB
Total Installed Size:  8.88 MiB

:: Proceed with installation? [Y/n] y
:: Retrieving packages...
 yay-12.3.1-1-aarch64                                                                                 3.3 MiB  2.33 MiB/s 00:01 [#############################################################################] 100%
(1/1) checking keys in keyring                                                                                                  [#############################################################################] 100%
(1/1) checking package integrity                                                                                                [#############################################################################] 100%
(1/1) loading package files                                                                                                     [#############################################################################] 100%
(1/1) checking for file conflicts                                                                                               [#############################################################################] 100%
(1/1) checking available disk space                                                                                             [#############################################################################] 100%
:: Processing package changes...
(1/1) installing yay                                                                                                            [#############################################################################] 100%
Optional dependencies for yay
    sudo: privilege elevation [installed]
    doas: privilege elevation
:: Running post-transaction hooks...
(1/1) Arming ConditionNeedsUpdate...
[ancapepe@ChoppaServer-1 ~]$ sudo pacman -S containerd fakeroot
...
(More of the same)
...
[ancapepe@ChoppaServer-1 ~]$ yay -S k3s-bin 
AUR Explicit (1): k3s-bin-1.30.3+k3s1-1
...
(Too much to show here. Just follow along)
...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Do that for each board. Teaching how to use a terminal is outside the scope of this guide)&lt;/p&gt;

&lt;p&gt;Hopefully everything went well and you have &lt;strong&gt;K3s&lt;/strong&gt; installed on all your servers, but it's still not doing anything. &lt;strong&gt;Kubernetes&lt;/strong&gt; applies the concepts of &lt;strong&gt;master&lt;/strong&gt; and &lt;strong&gt;worker&lt;/strong&gt; nodes: masters coordinate workers so that all can work as a single system, namely a &lt;a href="https://kubernetes.io/docs/concepts/architecture/" rel="noopener noreferrer"&gt;&lt;strong&gt;Cluster&lt;/strong&gt;&lt;/a&gt;; both may dot the heavy lifting of running applications; &lt;a href="https://kubernetes.io/docs/concepts/architecture/nodes/" rel="noopener noreferrer"&gt;&lt;strong&gt;Node&lt;/strong&gt;&lt;/a&gt; is simply the abstraction used for each [physical or virtual] computer&lt;/p&gt;

&lt;p&gt;[AFAIK] With &lt;strong&gt;K3s&lt;/strong&gt; things are kept simple and only your first node will run as a master, or &lt;strong&gt;[K3s] server&lt;/strong&gt; in this case (yes, terminology gets confusing). In order to initialize it as such when your main machine boots, open your &lt;strong&gt;K3s&lt;/strong&gt; &lt;a href="https://en.wikipedia.org/wiki/Init" rel="noopener noreferrer"&gt;&lt;strong&gt;init&lt;/strong&gt;&lt;/a&gt; file for modification with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl edit k3s &lt;span class="nt"&gt;--full&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The file will be opened with the application defined in your &lt;strong&gt;$EDITOR&lt;/strong&gt; &lt;a href="https://en.wikipedia.org/wiki/Environment_variable" rel="noopener noreferrer"&gt;environment variable&lt;/a&gt;, be it &lt;strong&gt;vim&lt;/strong&gt;, &lt;strong&gt;emacs&lt;/strong&gt; or even &lt;strong&gt;nano&lt;/strong&gt;, a competition I don't want to be dragged into. Talking about undesired fights, I know &lt;strong&gt;systemd&lt;/strong&gt; is not the only init system out there, but I haven't tried this with distros that use &lt;strong&gt;OpenRC&lt;/strong&gt; or &lt;strong&gt;Upstart&lt;/strong&gt;)&lt;/p&gt;

&lt;p&gt;Edit the line starting with &lt;code&gt;ExecStart&lt;/code&gt; to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/k3s server &lt;span class="nt"&gt;--write-kubeconfig-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;644 &lt;span class="nt"&gt;--node-name&lt;/span&gt; &amp;lt;your chosen master node name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally enable the initialization service with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;containerd
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;k3s &lt;span class="nt"&gt;--now&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not quite finally, actually. Before you restart your board to bring up &lt;strong&gt;K3s&lt;/strong&gt;, you have to make sure that the &lt;a href="https://en.wikipedia.org/wiki/Cgroups" rel="noopener noreferrer"&gt;&lt;strong&gt;cgroups&lt;/strong&gt;&lt;/a&gt; feature, required by all &lt;strong&gt;containerized&lt;/strong&gt; applications, is enabled and available in your system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;grep &lt;/span&gt;cgroup /proc/mounts
cgroup2 /sys/fs/cgroup cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot 0 0
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cgroups 
&lt;span class="c"&gt;#subsys_name    hierarchy       num_cgroups     enabled &lt;/span&gt;
cpuset  0       51      1 
cpu     0       51      1 
cpuacct 0       51      1 
blkio   0       51      1 
memory  0       51      0 
devices 0       51      1 
freezer 0       51      1 
net_cls 0       51      1 
perf_event      0       51      1 
net_prio        0       51      1 
pids    0       51      1 
rdma    0       51      1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(TL;DR: &lt;strong&gt;Cgroups&lt;/strong&gt; system &lt;strong&gt;v2&lt;/strong&gt; running, &lt;strong&gt;memory&lt;/strong&gt; cgroup disabled)&lt;/p&gt;

&lt;p&gt;If &lt;strong&gt;v1&lt;/strong&gt; system or none at all appears, you're probably using an old &lt;a href="https://en.wikipedia.org/wiki/Kernel_(operating_system)" rel="noopener noreferrer"&gt;&lt;strong&gt;kernel&lt;/strong&gt;&lt;/a&gt;, so update it. If the &lt;strong&gt;memory&lt;/strong&gt; cgroup is not enabled, you have to find the particular way in which you switch it on for your device (for &lt;strong&gt;Raspberry Pi&lt;/strong&gt;, that means adding &lt;code&gt;cgroup_enable=memory&lt;/code&gt; at the end of the single line in the &lt;code&gt;/boot/cmdline.txt&lt;/code&gt; file)&lt;/p&gt;

&lt;p&gt;Moreover, we must tell &lt;strong&gt;containerd&lt;/strong&gt;, the &lt;a href="https://en.wikipedia.org/wiki/Daemon_(computing)" rel="noopener noreferrer"&gt;&lt;strong&gt;daemon&lt;/strong&gt;&lt;/a&gt; that manages your containers behind the curtains, to use the right cgroup for its main process, &lt;strong&gt;runc&lt;/strong&gt;, by running the commands below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a containerd configuration directory&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/containerd
&lt;span class="c"&gt;# Write the default settings to a configuration file&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;containerd config default | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/containerd/config.toml
&lt;span class="c"&gt;# Find and replace the given string inside the file&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;ancapepe@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/            SystemdCgroup = false/            SystemdCgroup = true/'&lt;/span&gt; /etc/containerd/config.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The &lt;code&gt;#&lt;/code&gt; character at the beginning of a line denotes a comment, which is not supposed to be typed. For more information on what you're doing there consult &lt;a href="https://www.nocentino.com/posts/2021-12-27-installing-and-configuring-containerd-as-a-kubernetes-container-runtime/" rel="noopener noreferrer"&gt;this&lt;/a&gt; article and the &lt;a href="https://www.geeksforgeeks.org/sed-command-in-linux-unix-with-examples/" rel="noopener noreferrer"&gt;&lt;strong&gt;sed&lt;/strong&gt;&lt;/a&gt; command documentation)&lt;/p&gt;

&lt;p&gt;Now you may reboot the &lt;strong&gt;master&lt;/strong&gt; node, and hopefully everything will start running properly. Your &lt;strong&gt;SSH&lt;/strong&gt; connection will be terminated so it should be established again when the device finishes starting up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;leonardo@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot 
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; password &lt;span class="k"&gt;for &lt;/span&gt;leonardo:  

Broadcast message from root@ChoppaServer-1 on pts/1 &lt;span class="o"&gt;(&lt;/span&gt;Sat 2024-09-14 20:31:07 &lt;span class="nt"&gt;-03&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;: 

The system will reboot now! 

&lt;span class="o"&gt;[&lt;/span&gt;leonardo@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;Read from remote host 192.168.3.10: Connection reset by peer 
Connection to 192.168.3.10 closed. 
client_loop: send disconnect: Broken pipe 
&lt;span class="nv"&gt;$ &lt;/span&gt;ssh leonardo@192.168.3.10    
leonardo@192.168.3.10&lt;span class="s1"&gt;'s password: 
Welcome to Manjaro ARM 
~~Website: https://manjaro.org 
~~Forum:   https://forum.manjaro.org/c/arm 
~~Matrix:  #manjaro-arm-public:matrix.org 
Last login: Sat Sep 14 17:30:32 2024 from 192.168.3.8 
[leonardo@ChoppaServer-1 ~]$
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(At this point you may check the status of the enabled services using &lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemctl.html#status%20PATTERN%E2%80%A6%7CPID%E2%80%A6%5D" rel="noopener noreferrer"&gt;&lt;strong&gt;systemctl status&lt;/strong&gt;&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;With the &lt;strong&gt;master&lt;/strong&gt; system set up, let's try connecting our &lt;strong&gt;worker&lt;/strong&gt; nodes to it. That connection is established by sharing a common key or &lt;strong&gt;token&lt;/strong&gt; generated by master and stored in a predetermined file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Displays content of the token file&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;leonardo@ChoppaServer-1 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /var/lib/rancher/k3s/server/token 
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; password &lt;span class="k"&gt;for &lt;/span&gt;leonardo:  
K10d4e8b232cbf03832752443a07cc6206e092733c8ae55c0e64407dcb2e1775f45::server:44a1a8b25c4a8f1d8a0d48164eff2303
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy that file, guard it well, and repeat the same process (since the first ssh command) for each one of your workers, only changing the way the &lt;strong&gt;K3s&lt;/strong&gt; service file is edited, as now it should contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ExecStart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/k3s agent &lt;span class="nt"&gt;--server&lt;/span&gt; https://&amp;lt;your master node IP&amp;gt;:6443 &lt;span class="nt"&gt;--token&lt;/span&gt; &amp;lt;your master token&amp;gt; &lt;span class="nt"&gt;--node-name&lt;/span&gt; &amp;lt;your chosen worker node name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take your time in repeating all that stuff, just make sure that you follow through. If everything works as intended, from any one of your nodes you'd be able to run the command below successfully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;leonardo@ChoppaServer-2 ~]&lt;span class="nv"&gt;$ &lt;/span&gt;k3s kubectl get nodes 
NAME              STATUS   ROLES                  AGE    VERSION 
odroidn2-master   Ready    control-plane,master   7d5h   v1.30.3+k3s1 
rpi4-agent        Ready    &amp;lt;none&amp;gt;                 8d     v1.30.3+k3s1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Yay!)&lt;/p&gt;

&lt;p&gt;That's it for now. I hope it wasn't very tiring&lt;/p&gt;

&lt;p&gt;Next time we'll actually deploy applications and get a proper way to manage them&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/ancapepe/the-home-server-journey-2-the-control-room-fng"&gt;&lt;strong&gt;Chapter 2&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>censorship</category>
      <category>decentralization</category>
      <category>selfhosting</category>
    </item>
  </channel>
</rss>
