<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://wiki.bathcs.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Hw2210</id>
	<title>Bath Wiki - User contributions [en-gb]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.bathcs.com/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Hw2210"/>
	<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/wiki/Special:Contributions/Hw2210"/>
	<updated>2026-08-26T06:10:59Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Terraform&amp;diff=185</id>
		<title>Terraform</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Terraform&amp;diff=185"/>
		<updated>2026-08-25T20:41:34Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* Module structure */ Correct filenames in structure&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[BOSS]] uses terraform (actually [https://opentofu.org/ &amp;lt;code&amp;gt;opentofu&amp;lt;/code&amp;gt;]) to deploy all the resources onto [[BOSS/Hosting/Cluster|their kubernetes cluster]], which tries to be the language in which you can deploy anything and everything and if you need to quickly redeploy a whole machine you can with a simple command. In practise it doesn’t work like that, but its good enough for our needs.&lt;br /&gt;
&lt;br /&gt;
You can find the [https://gitlab.bath.ac.uk/cs/int/terraform terraform repo on gitlab], this page was originally taken from the README as it got too long.&lt;br /&gt;
&lt;br /&gt;
== Other resources ==&lt;br /&gt;
&lt;br /&gt;
* [https://developer.hashicorp.com/terraform/tutorials Hashicorp (the maker of the closed source terraform) has some good tutorials on their site]&lt;br /&gt;
&lt;br /&gt;
Basically that’s it really, the rest of this will be looking at how to deploy an application with our configuration.&lt;br /&gt;
&lt;br /&gt;
== Terraform and its woes ==&lt;br /&gt;
&lt;br /&gt;
Before we get into making an app, I must briefly explain terraform and its benefits/issues/confusing behaviours.&lt;br /&gt;
&lt;br /&gt;
This expects you to have a rough idea around how terraform works. But here is a quick explainer: terraform is build around &#039;&#039;&#039;resources&#039;&#039;&#039;, provided by &#039;&#039;&#039;providers&#039;&#039;&#039;. These resources have a state stored locally in a state file, whether they are deployed, generated values etc. (note that these can literally be anything e.g. from random passwords to HTTP reequests to kubernetes resources to DNS records). These resources can then be organised into modules, which (can) have outputs from values generated by the resources. There are also “data”, but this is basically a reference to another resource which doesn’t have the controls.&lt;br /&gt;
&lt;br /&gt;
When deploying, terraform will then check the state of all the current deployed modules (even pinging servers if needed) and find anything that has changed (e.g. new resources or updated values) and deploy everything.&lt;br /&gt;
&lt;br /&gt;
=== Module structure ===&lt;br /&gt;
&lt;br /&gt;
Modules are the core of terraform and can be a bit tricky to get your head around, as initially they are quite limited (e.g. there is no such thing as a global variable).&lt;br /&gt;
&lt;br /&gt;
But basically a module has a list of inputs as and a list of outputs (and then providers). So it is expected that your module deploys some resources which are then used to output something. E.g. in [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/ldap/user|our ldap user module] the module generates a random password, creates the user and assigns them to a group, then outputs the username, email and password to be used later in another resource.&lt;br /&gt;
&lt;br /&gt;
So e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;user&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ldap/user&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  display   = &amp;quot;Example&amp;quot;&lt;br /&gt;
  username  = &amp;quot;example&amp;quot;&lt;br /&gt;
  group_ids = var.ldap_group_ids&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    // Note passing providers act somewhat like global constants, passing configuration (e.g. what ldap server we mean)&lt;br /&gt;
    // to the module&lt;br /&gt;
    lldap = lldap&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// You can then use the email by: module.user.email or password: module.user.password&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;: All files within a module (the folder) are treated as one global space, similar to how Go works. You can reference variables, locals and resources throughout all files within a module which makes it quite difficult to organise nicely.&lt;br /&gt;
&lt;br /&gt;
The modules are usually structure in the way:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;main.tf&amp;lt;/code&amp;gt; – Usually where your &amp;lt;code&amp;gt;providers&amp;lt;/code&amp;gt; go &amp;lt;s&amp;gt;and if you are lazy (like me), everything else&amp;lt;/s&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;variables.tf&amp;lt;/code&amp;gt; – Where you put all your variables (even if there are none). As explained in [[#Variable madness|Variable madness]], I really don’t like this but we have decided to comply with terraform😞.&lt;br /&gt;
* &amp;lt;code&amp;gt;outputs.tf&amp;lt;/code&amp;gt; – All your outputs go here (even if there are none)&lt;br /&gt;
* &amp;lt;code&amp;gt;*.tf&amp;lt;/code&amp;gt; – Anything else, if you want to split it out nicely into other files&lt;br /&gt;
&lt;br /&gt;
=== Variable madness ===&lt;br /&gt;
&lt;br /&gt;
Terraform variables suck.&lt;br /&gt;
&lt;br /&gt;
Anyway, so basically terraform requires you do a full definition for every variable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;variable &amp;quot;my_var&amp;quot; {&lt;br /&gt;
  type = string&lt;br /&gt;
&lt;br /&gt;
  description = &amp;quot;Something&amp;quot;&lt;br /&gt;
  nullable = false&lt;br /&gt;
&lt;br /&gt;
  sensitive = false # If something is sensitive MAKE THIS TRUE&lt;br /&gt;
}&lt;br /&gt;
// You can then later reference it with var.my_var&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This defines something that must be inputed by the user, either through the module or your &amp;lt;code&amp;gt;tfvars&amp;lt;/code&amp;gt; file (if in the root directory).&lt;br /&gt;
&lt;br /&gt;
Due to this verbosity, and sometimes complex nature of the interfaces I like to create, I have used the &amp;lt;code&amp;gt;object&amp;lt;/code&amp;gt; type e.g.:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;variable &amp;quot;my_var&amp;quot; {&lt;br /&gt;
  type = object({&lt;br /&gt;
    l = list(string)&lt;br /&gt;
    m = map(bool) # string -&amp;gt; bool. Same syntax as object, just more flexible&lt;br /&gt;
    s = set(string) # Yes this is different to list but using the same []&lt;br /&gt;
    option = optional(string, &amp;quot;my_default&amp;quot;)&lt;br /&gt;
  })&lt;br /&gt;
  // ...&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
But even then you can’t define nice defaults for each sub item and the description is for the whole variable, so it has to be done as so. This is just raw pain and not particularly great syntax in my opinion. Also you cannot enable/disable sensitive nature of values for sub items, this means the whole object must be defined as sensitive if you have one password.&lt;br /&gt;
&lt;br /&gt;
And due to the lack of global constants, you must define every variable in every sub project and duplicate the types (yes there is &#039;&#039;&#039;no&#039;&#039;&#039; way to define a type to use throughout the project).&lt;br /&gt;
&lt;br /&gt;
It is also recommended that you put all variables in a &amp;lt;code&amp;gt;vars.tf&amp;lt;/code&amp;gt; file. Which sure does make sense for small modules, but if its that small I find it easier to just chuck at the top of the &amp;lt;code&amp;gt;init.tf&amp;lt;/code&amp;gt; file (as the terraform syntax highlighter is soooo broken). Then if its large, I find it more useful to put the variables where they are actually used – but then again this is confusing because the syntax and tooling is so bad.&lt;br /&gt;
&lt;br /&gt;
Oh yeah sorry and then there are &#039;&#039;&#039;locals&#039;&#039;&#039; which are constants you can define from resources/variables and will be calculated when the information is ready. E.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;locals {&lt;br /&gt;
  temp_val = &amp;quot;hi&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// Then you can reference with local.temp_val&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Depends on and its pains ===&lt;br /&gt;
&lt;br /&gt;
The one issue with terraform is that its really slow with large projects with this. But the nature of the design encourages large projects (as you want to reference things throughout the smaller apps).&lt;br /&gt;
&lt;br /&gt;
This is due to it having to create a dependency graph where objects wait on their dependencies. These dependencies can be defined by &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt; in any resource or just referencing a value from another resource.&lt;br /&gt;
&lt;br /&gt;
This is really useful so deployments are not actually deployed until all the secrets are deployed. Due to my perferable of not repeating myself, I heavily use the inferred dependency from the referring to resource names e.g. &amp;lt;code&amp;gt;kubernetes_secret_v1.secret.metadata[0].name&amp;lt;/code&amp;gt; (yes this is why I don’t just do the simple thing and use the shorter name, its good to know where the value comes from).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT&#039;&#039;&#039; you cannot rely on this, as some things take time to actually deploy even if it says its successful. Therefore you may need to use timers instead. I usually don’t bother due to the numerous other issues with terraform making it so its not actually perfect so there’s minimal point actually making it easy to deploy from scratch.&lt;br /&gt;
&lt;br /&gt;
=== Timeouts ===&lt;br /&gt;
&lt;br /&gt;
If something goes wrong during deployment, e.g. you make a typo, you will have to wait the FULL timeout time. This is really painful when you typo the hostname to the db causing the pod to crashloop in a helm config and you have to wait 10 minutes for terraform to give up. You can Ctrl-C, Ctrl-C, but this causes more issues as you will have to manually intervene and delete the helm chart/deployment before you run the command again.&lt;br /&gt;
&lt;br /&gt;
Instead I recommend shortening the timeouts for the deployment/helm to one more applicable to the application. A lot of our first-party stuff usually deploys in a few seconds and if it doesn’t, something has gone very wrong.&lt;br /&gt;
&lt;br /&gt;
=== Commas or no commas? ===&lt;br /&gt;
&lt;br /&gt;
The terraform syntax is… interesting. Commas are optional in most cases. So I would recommend, not typing commas where they optional.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT&#039;&#039;&#039; within lists/sets (basically between &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt;) you have to type commas, and if this is across multiple lines &#039;&#039;&#039;PLEASE ADD TRAILING COMMAS&#039;&#039;&#039;. The reason? Git histories look sooooooo much better.&lt;br /&gt;
&lt;br /&gt;
=== The recreation footgun ===&lt;br /&gt;
&lt;br /&gt;
If you are new to terraform, you might make the following change without thinking too much:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;aws_db_instance&amp;quot; &amp;quot;old_name&amp;quot; {&lt;br /&gt;
  # configuration goes here&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
into&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;aws_db_instance&amp;quot; &amp;quot;cool_new_name&amp;quot; {&lt;br /&gt;
  # EXACT SAME configuration goes here&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This will actually completely wipe that database instance from the face of the planet if you just blindly run &amp;lt;code&amp;gt;tofu plan&amp;lt;/code&amp;gt; then &amp;lt;code&amp;gt;tofu apply&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
What terraform actually does is:&lt;br /&gt;
&lt;br /&gt;
# Oh, &amp;lt;code&amp;gt;old_name&amp;lt;/code&amp;gt; doesn&#039;t exist anymore? I&#039;ll delete that instance then.&lt;br /&gt;
# Ah, a new &amp;lt;code&amp;gt;cool_new_name&amp;lt;/code&amp;gt; instance! I&#039;ll make that fresh then.&lt;br /&gt;
&lt;br /&gt;
You are now left with no data (unless you made backups).&lt;br /&gt;
&lt;br /&gt;
To avoid this, we have a couple options. The first and generally preferred option is to use a [https://developer.hashicorp.com/terraform/language/block/moved &amp;lt;code&amp;gt;moved&amp;lt;/code&amp;gt; block]. For the above example, we can do:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
moved {&lt;br /&gt;
  from = aws_db_instance.old_name&lt;br /&gt;
  to = aws_db_instance.cool_new_name&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;aws_db_instance&amp;quot; &amp;quot;cool_new_name&amp;quot; {&lt;br /&gt;
  # original configuration goes here&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These may litter the code and so they should be removed after a while and are only really preferred if multiple people are working on the terraform. For example, it shows a reviewer that you&#039;ve renamed a resource and if they deploy it, they won&#039;t mess it up.&lt;br /&gt;
&lt;br /&gt;
The other option is to rename it in the code and just run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu state mv aws_db_instance.old_name aws_db_instance.cool_new_name&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then, when you &amp;lt;code&amp;gt;tofu plan&amp;lt;/code&amp;gt; you shouldn&#039;t see any deletion for the old resource.&lt;br /&gt;
&lt;br /&gt;
== How to create a basic project ==&lt;br /&gt;
&lt;br /&gt;
=== Creating a namespace ===&lt;br /&gt;
&lt;br /&gt;
There is a handy util module for this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;example_namespace&amp;quot; {&lt;br /&gt;
  source = &amp;quot;./utils/namespace&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  name = &amp;quot;example&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  enable_dns = true&lt;br /&gt;
  enable_mail = true&lt;br /&gt;
  enable_lldap = true&lt;br /&gt;
  bkp = {&lt;br /&gt;
    // ...&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This allows you to enable or disable features for your namespace, e.g. if your pods need to communicate with the outside world, enabling the DNS. All of these features are disabled by default and its heavily encouraged to only enable the features if the namespace needs it.&lt;br /&gt;
&lt;br /&gt;
The next thing is to configure backups through this, to reduce our dependence and costs from our s3 provider, it is recommended that backups are disabled for all namespaces whos data can be regenerated (e.g. froom). If you do enable it, it is then encouraged that you disable backups for any database or pvc that you don’t need backing up with the &amp;lt;code&amp;gt;k8up.io/backup=false&amp;lt;/code&amp;gt; annotation (you may notice that all &amp;lt;code&amp;gt;valkey&amp;lt;/code&amp;gt; instances set this by default if you are using the &amp;lt;code&amp;gt;app/valkey&amp;lt;/code&amp;gt; module).&lt;br /&gt;
&lt;br /&gt;
==== Placement ====&lt;br /&gt;
&lt;br /&gt;
Within this repository, it is tradition to put the namespace creation at the highest level, e.g. &amp;lt;code&amp;gt;20_apps.tf&amp;lt;/code&amp;gt;. This means that the apps themselves do not control the namespace they are created in. It is mostly just a personal preference from me after years of configuring k8s on terraform.&lt;br /&gt;
&lt;br /&gt;
=== Using Helm ===&lt;br /&gt;
&lt;br /&gt;
Helm is by far the easiest way to deploy third-party tools, and is used throught this repo despite it’s drawbacks when combined with terraform (it’s just so easy).&lt;br /&gt;
&lt;br /&gt;
You just add helm to the providers list (which defines what terraform modules you are integrating with):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;terraform {&lt;br /&gt;
  required_providers {&lt;br /&gt;
    helm = {&lt;br /&gt;
      source  = &amp;quot;hashicorp/helm&amp;quot;&lt;br /&gt;
      version = &amp;quot;~&amp;gt;3.1.1&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then use the &amp;lt;code&amp;gt;helm_release&amp;lt;/code&amp;gt; resource, which takes the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;helm_release&amp;quot; &amp;quot;my_app&amp;quot; {&lt;br /&gt;
  name      = &amp;quot;my_app&amp;quot;&lt;br /&gt;
  namespace = var.namespace&lt;br /&gt;
&lt;br /&gt;
  repository = &amp;quot;https://charts.example.com&amp;quot;&lt;br /&gt;
  chart      = &amp;quot;the_app&amp;quot;&lt;br /&gt;
  version    = &amp;quot;version&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  values = [yamlencode({&lt;br /&gt;
    // Values go here written within the terraform config language&lt;br /&gt;
  })]&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This deploys all the resources an application needs and manages and restarting if a secret or config map changes, providing all the configuration at your fingertips.&lt;br /&gt;
&lt;br /&gt;
However this comes at a cost:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;SECRETS SHOULD NOT GO IN THE HELM CONFIG&#039;&#039;&#039;. This is a big one, all values are easily accessible unencrypted on the cluster, therefore any secrets &#039;&#039;&#039;MUST&#039;&#039;&#039; go in a &amp;lt;code&amp;gt;kubernetes_secrets_v1&amp;lt;/code&amp;gt; object and you should use a &amp;lt;code&amp;gt;secretsRef&amp;lt;/code&amp;gt; or similar to link it. If the helm chart does not support this &#039;&#039;&#039;DO NOT USE IT&#039;&#039;&#039;. Helm also stores the history of all values, therefore if you put it temporarily within helm values for testing, you must to a password rotation.&lt;br /&gt;
* You have no power over the types of resources and the structure in which it deploys. This means that if a feature or support for our strict network policies are not implemented, you have to either not use the helm chart completely or fork your own (which we definitely don’t want to do).&lt;br /&gt;
* If the helm chart gets deleted, all pvc related &#039;&#039;might&#039;&#039; also get deleted (unless they have the &amp;lt;code&amp;gt;Retain&amp;lt;/code&amp;gt; policy, which should be the case for everything).&lt;br /&gt;
* Sometimes they don’t have the proper security contexts/network policies by default so you will have to add them youself (see the below section)&lt;br /&gt;
&lt;br /&gt;
Overall, helm is pretty good, just use with caution and understand what templates you are inflicting. Note, you will probably have to get pretty good at reading not only default values, but schemas and the templating language of helm itself, as sometimes the charts are not particularly well documented.&lt;br /&gt;
&lt;br /&gt;
=== Using kubernetes ===&lt;br /&gt;
&lt;br /&gt;
For this you need to understand a bit of structure of how kubernetes works. I will assume that you are deploying a pod. If that pod needs storage attached (and not through SQL or Redis), then you will need to use a [https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ &amp;lt;code&amp;gt;StatefulSet&amp;lt;/code&amp;gt;]. If you have no storage or are just communicating with a postgres server or redis, you can instead use a [https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ &amp;lt;code&amp;gt;Deployment&amp;lt;/code&amp;gt;].&lt;br /&gt;
&lt;br /&gt;
The difference between these two concepts are not particularly seen in the world of a single node cluster, but basically deployments are free to spin up another even if the previous one is still terminating or if the node is non responsive. On the other hand, statefulsets must ensure that no two nodes are trying to access the same data, therefore cannot automatically start up if a node goes down.&lt;br /&gt;
&lt;br /&gt;
This also means that it is much easier to scale a deployment to multiple nodes, vs a statefulsets which must have separate volumes per pod.&lt;br /&gt;
&lt;br /&gt;
Anyway, both statefulsets and deployments have a template configuration for creating the pod associated with itself. This pod has a label which is used to monitor and track the associated pods with its parent. So the structure is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;my_deployment&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    replicas = 1&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = &amp;quot;the_deployment&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    // This doesn&#039;t really matter in a one node cluster with a replicas = 1&lt;br /&gt;
    strategy {&lt;br /&gt;
      type = &amp;quot;RollingUpdate&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = &amp;quot;the_deployment&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          name              = &amp;quot;my_deployment&amp;quot;&lt;br /&gt;
          image             = &amp;quot;bathbcss/my_image:latest&amp;quot;&lt;br /&gt;
          image_pull_policy = &amp;quot;Always&amp;quot; // Should only be set if the above is &amp;quot;latest&amp;quot;&lt;br /&gt;
&lt;br /&gt;
          // This should be the default security context to comply with our pod security policies&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // This is how the pod is checked its alive, so if something happens,&lt;br /&gt;
          // e.g. job which causes it to become unresponsive, it will be automatically killed off and replaced&lt;br /&gt;
          liveness_probe {&lt;br /&gt;
            http_get {&lt;br /&gt;
              path = &amp;quot;/healthz&amp;quot;&lt;br /&gt;
              port = 8080&lt;br /&gt;
            }&lt;br /&gt;
            initial_delay_seconds = 5&lt;br /&gt;
            period_seconds        = 10&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // This allows the tracking of when the pod starts, so we wait until the pod is ready to receive requests&lt;br /&gt;
          startup_probe {&lt;br /&gt;
            http_get {&lt;br /&gt;
              path = &amp;quot;/healthz&amp;quot;&lt;br /&gt;
              port = 8080&lt;br /&gt;
            }&lt;br /&gt;
            // 3 * 30 = 90 seconds to start&lt;br /&gt;
            failure_threshold = 30&lt;br /&gt;
            // If it takes a while to startup, increase this time&lt;br /&gt;
            period_seconds = 3&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // If exposing a port the port to expose&lt;br /&gt;
          port {&lt;br /&gt;
            container_port = 8080&lt;br /&gt;
            // Make sure to give it a name so we can use the name in services&lt;br /&gt;
            name           = &amp;quot;web&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // env and env_from definitions&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;Quick side note: &amp;lt;code&amp;gt;kubernetes_\*_v1&amp;lt;/code&amp;gt;is the preferred resouce name, any resource that does not have&amp;lt;code&amp;gt;\_v1&amp;lt;/code&amp;gt; on the end is deprecated and should not be used.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
It is recommended that the version of the image is actually set and &amp;lt;code&amp;gt;latest&amp;lt;/code&amp;gt; is not used, however to reduce the admin overhead, for internal projects it can be easier to set to &amp;lt;code&amp;gt;latest&amp;lt;/code&amp;gt; with an image pull policy of &amp;lt;code&amp;gt;Always&amp;lt;/code&amp;gt;. However this means if you want the latest version, you must have access to the cluster to restart a pod.&lt;br /&gt;
&lt;br /&gt;
If you are exposing pods, this should be tied with a &amp;lt;code&amp;gt;Service&amp;lt;/code&amp;gt;, as seen below.&lt;br /&gt;
&lt;br /&gt;
==== Security context ====&lt;br /&gt;
&lt;br /&gt;
As you will notice in the example above, we have a security context set. This is &#039;&#039;&#039;required&#039;&#039;&#039; by the pod security contenxt, otherwise it will not deploy. In most cases you can copy either of the two following policies, depending on whether it is within a kubernetes resource or helm/kubernetes manifest resource:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  security_context {&lt;br /&gt;
    run_as_user                = 1000&lt;br /&gt;
    run_as_non_root            = true&lt;br /&gt;
    allow_privilege_escalation = false&lt;br /&gt;
    seccomp_profile {&lt;br /&gt;
      type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    capabilities {&lt;br /&gt;
      drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    securityContext = {&lt;br /&gt;
      runAsUser                = 1000&lt;br /&gt;
      runAsNonRoot             = true&lt;br /&gt;
      allowPrivilegeEscalation = false&lt;br /&gt;
      seccompProfile = {&lt;br /&gt;
        type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      capabilities = {&lt;br /&gt;
        drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that we are running as a user (not root), setting the default seccompProfile (you &#039;&#039;should&#039;&#039; only need the default unless you are doing weird things with the host machine) as well as dropping all capabilities (you may need to add some back in but I will leave to you as you probably know more than me – NOTE: Some are disabled by our pod security policy but can be override with &amp;lt;code&amp;gt;baseline&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==== Liveness and startup probe ====&lt;br /&gt;
&lt;br /&gt;
The liveness and startup probes are not necessary, but is a nice to have. The liveness probe allows the cluster to detect if a pod becomes unresponsive and is then able to kill it if that is the case. Whereas a startup probe makes it so the cluster knows exactly when the pod is able to receive responses.&lt;br /&gt;
&lt;br /&gt;
Please see the [https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ kubernetes docs on probes] for more information on the options. But in most cases the HTTP get option should suffice, which just looks for a status 2xx code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  liveness_probe {&lt;br /&gt;
    http_get {&lt;br /&gt;
      port = 8080&lt;br /&gt;
    }&lt;br /&gt;
    initial_delay_seconds = 5&lt;br /&gt;
    period_seconds        = 10&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  startup_probe {&lt;br /&gt;
    http_get {&lt;br /&gt;
      port = 8080&lt;br /&gt;
    }&lt;br /&gt;
    failure_threshold = 30&lt;br /&gt;
    period_seconds = 3&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    livenessProbe = {&lt;br /&gt;
      httpGet = {&lt;br /&gt;
        port = 8080&lt;br /&gt;
      }&lt;br /&gt;
      initialDelaySeconds = 5&lt;br /&gt;
      periodSeconds       = 10&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    startupProbe = {&lt;br /&gt;
      httpGet = {&lt;br /&gt;
        port = 8080&lt;br /&gt;
      }&lt;br /&gt;
      failureThreshold = 30&lt;br /&gt;
      periodSeconds    = 3&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Environmental variables ====&lt;br /&gt;
&lt;br /&gt;
When configuring deployments, you will want to set environmental variables. There are a few ways to do it, but note &#039;&#039;&#039;ANY PASSWORDS/API KEYS GO IN SECRETS&#039;&#039;&#039; not the environmental variables. As you will see I will example how to do this.&lt;br /&gt;
&lt;br /&gt;
By default the &amp;lt;code&amp;gt;env&amp;lt;/code&amp;gt; list can be used to set a single environmental variable e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env {&lt;br /&gt;
    name = &amp;quot;TEST&amp;quot;&lt;br /&gt;
    value = &amp;quot;my_value&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, this is quite verbose and takes a lot of space, so if you are configuring a lot of variables or have secrets, you will want to use the &amp;lt;code&amp;gt;env_from&amp;lt;/code&amp;gt; list. This allows you to reference a config map or secrets (this is the most basic form).&lt;br /&gt;
&lt;br /&gt;
These look like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_secret_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;deployment-db&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  data = {&lt;br /&gt;
    DATABASE_URL = module.database.url&lt;br /&gt;
  }&lt;br /&gt;
  type = &amp;quot;Opaque&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_config_map_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;deployment-config&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  data = {&lt;br /&gt;
    PUBLIC_VALUE   = &amp;quot;yoooo&amp;quot;&lt;br /&gt;
    ROCKET_ADDRESS = &amp;quot;::&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env_from {&lt;br /&gt;
    secret_ref {&lt;br /&gt;
      name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  env_from {&lt;br /&gt;
    config_map_ref {&lt;br /&gt;
      name = kubernetes_config_map_v1.module.metadata[0].name&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    envFrom = [&lt;br /&gt;
      {&lt;br /&gt;
        secretRef = {&lt;br /&gt;
          name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
        configMapRef = {&lt;br /&gt;
          name = kubernetes_config_map_v1.module.metadata[0].name&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
    ]&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that you can set the value of a environmental variable from a secret on an individual basis, which can be useful if you are storing environmental variables as well as files inside your secret. E.g:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env {&lt;br /&gt;
    name = &amp;quot;DB_PASSWORD&amp;quot;&lt;br /&gt;
    value_from {&lt;br /&gt;
      secret_key_ref {&lt;br /&gt;
        name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
        key  = &amp;quot;password&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Third-party CRDs ====&lt;br /&gt;
&lt;br /&gt;
Now this is is where terraform becomes less good. Basically when deploying using the kubernetes API, the checks will make sure the CRDs (so the like api and kind are installed and supported on the kubernetes cluster). This means that you won’t even be able to run.&lt;br /&gt;
&lt;br /&gt;
Basically it means that you need to comment out manifests that reference these resources until the CRDs are deployed (usually though a helm chart or something).&lt;br /&gt;
&lt;br /&gt;
==== PVCs ====&lt;br /&gt;
&lt;br /&gt;
Please remember &#039;&#039;&#039;ANY PASSWORDS/API KEYS/CERTIFICATES GO IN SECRETS&#039;&#039;&#039; not in the storage (this also means its configurable by us and yes they can be mounted as read only volumes).&lt;br /&gt;
&lt;br /&gt;
So, kubernetes storage works around persistant volumes which are requested by persistant volume claims. On our k3s single node, we are just using the k3s filesystem class. This means it’s a bit basic but does the job.&lt;br /&gt;
&lt;br /&gt;
Things to note:&lt;br /&gt;
&lt;br /&gt;
* You probably should be manually creating persistant volume claims (and definitely not persistant volumes), instead using &amp;lt;code&amp;gt;statefulsets&amp;lt;/code&amp;gt;&lt;br /&gt;
* It’s really hard to change persistant volumes post fact, so please go through testing phase if you are unsure about anything.&lt;br /&gt;
* K3s does not support the storage limit, so please &#039;&#039;&#039;DON’T RELY ON IT&#039;&#039;&#039; to stop abusive behaviour.&lt;br /&gt;
* If you are defining yourself, do not accidentally make your deployment depend on the persistant volume claim, as the pvc will not be created until it is used in something. See [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/services/mail/mailserver.tf|the mailserver module] of how to handle it. &#039;&#039;&#039;NOTE&#039;&#039;&#039;: If you reference the pvc config in your deployment, terraform will add that automatically to the &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt; list.&lt;br /&gt;
* If it is critical data you will need to &#039;&#039;&#039;manually update the pv to “retain” its data&#039;&#039;&#039; if the pvc gets deleted. This just adds a bit of safety if you mess up a deployment. This can be done in &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; by an admin, updating the &amp;lt;code&amp;gt;persistentVolumeReclaimPolicy&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;Retain&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Delete&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
So how you should be using pvc, in statefulsets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_stateful_set_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    service_name = var.name&lt;br /&gt;
    replicas     = 1&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = var.name&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = var.name&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          name  = &amp;quot;my_app&amp;quot;&lt;br /&gt;
          image = &amp;quot;bathbcss/my_app:1.0.0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
          // ...&lt;br /&gt;
&lt;br /&gt;
          volume_mount {&lt;br /&gt;
            name       = &amp;quot;data&amp;quot;&lt;br /&gt;
            mount_path = &amp;quot;/data&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    volume_claim_template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        name = &amp;quot;data&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        access_modes = [&amp;quot;ReadWriteOnce&amp;quot;]&lt;br /&gt;
        resources {&lt;br /&gt;
          requests = {&lt;br /&gt;
            storage = &amp;quot;10Gi&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Services ====&lt;br /&gt;
&lt;br /&gt;
Servies are the first way to adding an ingress, and helps give a common endpoint if you have multiple pods running (though we don’t do this). If using within the cluster you can access a service with the address &amp;lt;code&amp;gt;service_name.namespace.svc.cluster.local&amp;lt;/code&amp;gt;. Note that if you are accessing it from within the same namespace you can just use the &amp;lt;code&amp;gt;service_name&amp;lt;/code&amp;gt; as the hostname (and it is better on a network policy basis).&lt;br /&gt;
&lt;br /&gt;
For a namespace you just need a label to select on (note we have to define an &amp;lt;code&amp;gt;app&amp;lt;/code&amp;gt; label for the statefulset/deployment anyway so you can just use this).&lt;br /&gt;
&lt;br /&gt;
So it should look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_service_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    port {&lt;br /&gt;
      port = 8080&lt;br /&gt;
      name = &amp;quot;web&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    selector = {&lt;br /&gt;
      app = var.name&lt;br /&gt;
    }&lt;br /&gt;
    type = &amp;quot;ClusterIP&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And then you can just access the port by &amp;lt;code&amp;gt;service_name:8080&amp;lt;/code&amp;gt;. Note that you can also set a &amp;lt;code&amp;gt;target_port&amp;lt;/code&amp;gt; if you want to change the port from the deployment and service (but like why?).&lt;br /&gt;
&lt;br /&gt;
Also giving it a &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; is important, so in the ingress we can just use the name instead of the port number itself, increasing readability.&lt;br /&gt;
&lt;br /&gt;
==== Network policies ====&lt;br /&gt;
&lt;br /&gt;
Now comes the pain. If a helm chart has a network policy, use that (but please actually read what permissions it gives).&lt;br /&gt;
&lt;br /&gt;
Network policies, either grant, or block network “ingress” (stuff going into the pod) and “egress” (stuff going out of the pod). By default, due to our security, ingress to our pods is denied (so anything in the cluster), but egress to the outside world is allowed.&lt;br /&gt;
&lt;br /&gt;
You then use either pod selectors or namespace selectors to allow traffic from a pod (which is how &amp;lt;code&amp;gt;enable_dns,lldap,mail&amp;lt;/code&amp;gt; works). In most cases egress can be left alone, unless you want to block a node from doing something in particular.&lt;br /&gt;
&lt;br /&gt;
As talked about in the next section, for postgres and valkey, we create these policies, limited to pods with the &amp;lt;code&amp;gt;${name}-${service}-client=true&amp;lt;/code&amp;gt; label allowing only inter-namespace communication.&lt;br /&gt;
&lt;br /&gt;
If you do have to write one, which I really hope you don’t, please read [https://kubernetes.io/docs/concepts/services-networking/network-policies/ kubernetes documentation on Network Policies].&lt;br /&gt;
&lt;br /&gt;
=== Postgres and Redis/Valkey ===&lt;br /&gt;
&lt;br /&gt;
We have utilities for [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/apps/postgres|postgres] and redis (through [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/apps/valkey|valkey] due to redis being really hard to run).&lt;br /&gt;
&lt;br /&gt;
If you require these, we highly recommend the above (though postgres needs moving away from bitnami due to the requirement of money for stability). These setup the network policies you need as well as generating secure passwords and you can look at the &amp;lt;code&amp;gt;outputs.tf&amp;lt;/code&amp;gt; file to see the outputs you can use from a module.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; If using this, for any pod accessing the data, it must be in the same namespace and have the labels &amp;lt;code&amp;gt;${name}-postgresql-client=true&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;${name}-valkey-client=true&amp;lt;/code&amp;gt;, otherwise the traffic will be denied by the network policies.&lt;br /&gt;
&lt;br /&gt;
E.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;database&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../postgres&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  namespace = var.namespace&lt;br /&gt;
  prefix    = var.name&lt;br /&gt;
  username  = &amp;quot;user&amp;quot;&lt;br /&gt;
  database  = &amp;quot;db_name&amp;quot;&lt;br /&gt;
  size      = &amp;quot;250Mi&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    helm = helm&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Create a secret with module.database.url&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = var.name&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = var.name&lt;br /&gt;
          // This **MUST** be defined otherwise you&#039;ll get weird errors&lt;br /&gt;
          froom-pg-postgresql-client = true&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // ...&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Side note: the namespace will also need to have &amp;lt;code&amp;gt;enable_dns&amp;lt;/code&amp;gt; to be able to access it&lt;br /&gt;
&lt;br /&gt;
=== Adding ingress ===&lt;br /&gt;
&lt;br /&gt;
Adding ingress to service basically means that you are making it accessible to the outside world.&lt;br /&gt;
&lt;br /&gt;
If you are doing this, please consider security heavily:&lt;br /&gt;
&lt;br /&gt;
* Can any user alter the DB? Are you doing proper type checking on the inputs?&lt;br /&gt;
* Do you make sure to not expose any secrets e.g. db urls&lt;br /&gt;
* Do you really need to expose this pod? Or can you leave it to k9s port forwarding?&lt;br /&gt;
&lt;br /&gt;
Then you need to ask:&lt;br /&gt;
&lt;br /&gt;
* Just expose it to people within the bath network e.g. on &amp;lt;code&amp;gt;*.k8s.bathcs.com&amp;lt;/code&amp;gt;&lt;br /&gt;
* Expose it to the whole world &amp;lt;code&amp;gt;*.bathcs.com&amp;lt;/code&amp;gt; (note coordination with backstage to get them to update their traefik will be necessary)&lt;br /&gt;
&lt;br /&gt;
Please expose to the utter minimum people.&lt;br /&gt;
&lt;br /&gt;
If exposing to the full network please note the flow is:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;app.bathcs.com&amp;lt;/code&amp;gt; &amp;amp;lt;- You should accept this domain in the ingress&lt;br /&gt;
* &amp;lt;code&amp;gt;app.bcss.su.bath.ac.uk&amp;lt;/code&amp;gt; &amp;amp;lt;- You should accept this domain in the ingress&lt;br /&gt;
* &amp;lt;code&amp;gt;app.k8s.bathcs.com&amp;lt;/code&amp;gt; &amp;amp;lt;- This is what the certificate you should be giving (due to this is hostname that backstage is requesting)&lt;br /&gt;
&lt;br /&gt;
Depending on the chosen level, please look at the [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/ingress|&amp;lt;code&amp;gt;utils/ingress/*&amp;lt;/code&amp;gt; modules] as these handle this most of this flow for you.&lt;br /&gt;
&lt;br /&gt;
E.g. making it internal to bath uni only:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;dns&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/dns_flow&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  subdomain = var.subdomain&lt;br /&gt;
  # We don&#039;t want public one, so don&#039;t specify cloudflare_zone_id&lt;br /&gt;
  k8s_cloudflare_zone_id = var.cloudflare_zone_id&lt;br /&gt;
&lt;br /&gt;
  # Expanded for effect&lt;br /&gt;
  domains = {&lt;br /&gt;
    uni = var.domains.uni&lt;br /&gt;
    k8s = var.domains.k8s&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    cloudflare = cloudflare,&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
module &amp;quot;ingress&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/tls&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  name                     = var.subdomain&lt;br /&gt;
  entry_points             = [&amp;quot;websecure&amp;quot;]&lt;br /&gt;
  namespace                = var.namespace&lt;br /&gt;
  host                     = module.dns.hosts.k8s&lt;br /&gt;
  additional_ingress_hosts = [module.dns.hosts.uni]&lt;br /&gt;
  service = {&lt;br /&gt;
    name = kubernetes_service_v1.module.metadata[0].name&lt;br /&gt;
    port = &amp;quot;http&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
  cert_issuer = var.cert_issuer&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes,&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If using a helm repo you may have to manually define the certificate, which is quite easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;cert&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    apiVersion = &amp;quot;cert-manager.io/v1&amp;quot;&lt;br /&gt;
    kind       = &amp;quot;Certificate&amp;quot;&lt;br /&gt;
    metadata = {&lt;br /&gt;
      name      = &amp;quot;${var.name}-cert&amp;quot;&lt;br /&gt;
      namespace = var.namespace&lt;br /&gt;
    }&lt;br /&gt;
    spec = {&lt;br /&gt;
      secretName = &amp;quot;${var.name}-cert-secret&amp;quot;&lt;br /&gt;
      issuerRef  = var.cert_issuer&lt;br /&gt;
      dnsNames   = [var.host]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which outputs the cert to the secret with name &amp;lt;code&amp;gt;${var.name}-cert-secret&amp;lt;/code&amp;gt;. You can also use &amp;lt;code&amp;gt;kubernetes_manifest.cert.manifest.spec.secretName&amp;lt;/code&amp;gt; (you can guess what I prefer).&lt;br /&gt;
&lt;br /&gt;
If you are making it publically accessible, you can use the full flow which does both dns and ingress records:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;ingress&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/full_flow&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  cloudflare_zone_id = var.cloudflare_zone_id&lt;br /&gt;
  namespace          = var.namespace&lt;br /&gt;
  domains            = var.domains&lt;br /&gt;
  subdomain          = var.subdomain&lt;br /&gt;
&lt;br /&gt;
  service = {&lt;br /&gt;
    name = kubernetes_service_v1.module.metadata[0].name&lt;br /&gt;
    port = &amp;quot;web&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
  cert_issuer = var.cert_issuer&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes,&lt;br /&gt;
    cloudflare = cloudflare&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Putting it behind authelia ====&lt;br /&gt;
&lt;br /&gt;
If the application doesn’t have oauth integration build in and you are wanting to protect it behind authelia you can add middleware of &amp;lt;code&amp;gt;auth-forwardauth-authelia@kubernetescrd&amp;lt;/code&amp;gt; to require them to go through authelia.&lt;br /&gt;
&lt;br /&gt;
You will then have to add an access control rule to authelia e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;rules = [&lt;br /&gt;
  {&lt;br /&gt;
    domain = &amp;amp;quot;app.k8s.bathcs.com&amp;amp;quot;&lt;br /&gt;
    subject = [&amp;amp;quot;group:boss-example-group&amp;amp;quot;, &amp;amp;quot;user:hw2210&amp;amp;quot;]&lt;br /&gt;
  },&lt;br /&gt;
]&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;: you can create and manage groups on [https://www.bath.ac.uk/groupmanager/ Bath&#039;s group manager], which creates a unix group that can be seen in the LDAP server.&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare dns records ===&lt;br /&gt;
&lt;br /&gt;
Cloudflare provider is an actual pain. You would’ve hoped it would be good, but its not amazing (has previously caused updates on every &amp;lt;code&amp;gt;apply&amp;lt;/code&amp;gt;). Basically when creating a dns record you should use the whole address e.g. &amp;lt;code&amp;gt;app.bathcs.com&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;app&amp;lt;/code&amp;gt;. This is because it will cause an update on the second application, changing the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; value (which we use to get the full domain).&lt;br /&gt;
&lt;br /&gt;
It sucks because theres an additional useless variable ontop of the &amp;lt;code&amp;gt;cloudflare_zone_id&amp;lt;/code&amp;gt; (yes you can use the “data” thing to solve this but still).&lt;br /&gt;
&lt;br /&gt;
Additionally for long TXT records, it will add additional quotes, and terraform will continually think you need to change that if you don’t yourself add the quotes into the data.&lt;br /&gt;
&lt;br /&gt;
=== Sending mail ===&lt;br /&gt;
&lt;br /&gt;
Sending mail is somewhat weird.&lt;br /&gt;
&lt;br /&gt;
Basically just because I can, the authentication is managed by &amp;lt;code&amp;gt;lldap&amp;lt;/code&amp;gt; (a really fast ldap implementation). This should not be confused with the ldap service that Authelia is hooked up with, as it is not.&lt;br /&gt;
&lt;br /&gt;
Once you’ve enabled mail in the namespace you then have to create an ldap user:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;user&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ldap/user&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  display   = &amp;quot;Example&amp;quot;&lt;br /&gt;
  username  = &amp;quot;example&amp;quot;&lt;br /&gt;
  group_ids = var.ldap_group_ids&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    lldap = lldap&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then use this to access the mail with &amp;lt;code&amp;gt;module.user.name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;module.user.password&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;module.user.email&amp;lt;/code&amp;gt;. &#039;&#039;&#039;NOTE&#039;&#039;&#039; due to it not being exposed, we must use the kubernetes domain certificate, which means that you normally have to disable tls verification it or set the expected domain to &amp;lt;code&amp;gt;bathcs.com&amp;lt;/code&amp;gt; (as seen in authelia).&lt;br /&gt;
&lt;br /&gt;
=== Creating an oauth client ===&lt;br /&gt;
&lt;br /&gt;
I have created a [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/oauth/client|OAuth client module] for generating all the secret data you need and generate the client config (under the &amp;lt;code&amp;gt;config&amp;lt;/code&amp;gt; output) you can then pass up and then into the authelia module (see &amp;lt;code&amp;gt;grafana&amp;lt;/code&amp;gt; as an example).&lt;br /&gt;
&lt;br /&gt;
But basically please read [https://www.authelia.com/integration/openid-connect/introduction/ the authelia oidc docs] for a full explanation of how it works.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Bath_Open_Source_Society&amp;diff=158</id>
		<title>Bath Open Source Society</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Bath_Open_Source_Society&amp;diff=158"/>
		<updated>2026-06-11T18:16:51Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* Get Started */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Bath Open Source Society (BOSS), is a society focused on providing computer services to students and societies. Their members and committee build and provide websites and tools which students and societies can use.&lt;br /&gt;
&lt;br /&gt;
All services are hosted under [https://bathcs.com bathcs.com], which is a domain owned by the society, as well as [https://bcss.su.bath.ac.uk bcss.su.bath.ac.uk].&lt;br /&gt;
&lt;br /&gt;
The committee&#039;s contact details can be found on their [https://thesubath.com/boss SU page].&lt;br /&gt;
&lt;br /&gt;
== Services ==&lt;br /&gt;
There are many services maintained by BOSS, students are free to use most of them, with only a few requiring permission.&lt;br /&gt;
&lt;br /&gt;
=== General ===&lt;br /&gt;
&lt;br /&gt;
* [https://froom.bathcs.com Froom], a tool to help you find free unbookable rooms on campus if you just need a room for brief meetings or some quite time.&lt;br /&gt;
* [[Main Page|Wiki]], this mediawiki instance, where students can store general knowledge and information about university life&lt;br /&gt;
&lt;br /&gt;
=== Society tools ===&lt;br /&gt;
&lt;br /&gt;
* [https://vault.bathcs.com VaultTub], a hosted password manager compatible with [https://bitwarden.com/ Bitwarden], allows societies to easily share password&#039;s with other committee members and perform handovers relatively simply. If your society wants to use this, please email [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk] and we can set you up with an organisation.&lt;br /&gt;
&lt;br /&gt;
* [https://signin.bathcs.com Sign me in!], a tool created for societies to check and track people signed up for a ticketed event and is compatible with the SU&#039;s library card reader for even faster signin process.&lt;br /&gt;
* [https://gitlab.bath.ac.uk/cs/services/timetable-heater Timetable Heatmap], a tool to find the best times for weekly events with a chosen audience. Please note that this does require some programming knowledge to be able to use.&lt;br /&gt;
&lt;br /&gt;
=== Departmental tools ===&lt;br /&gt;
We also have tools for general departmental student activity, if departments want to use these tools, please contact [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk] and we can set you up with it.&lt;br /&gt;
&lt;br /&gt;
* [https://gitlab.bath.ac.uk/cs/pm/peer-mentor-website Peer Mentor Grouper], this is a basic site that allows freshers to choose preferred peer mentors and then assigns the best combination to achieve most people&#039;s preferred choice.&lt;br /&gt;
&lt;br /&gt;
=== Custom ===&lt;br /&gt;
If a society wants a custom website or tool for an event they are running, please get in contact with [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk]. Though your dream may not be completely achieved, BOSS will try to fit your needs.&lt;br /&gt;
&lt;br /&gt;
With this, we are able to create and host services such as:&lt;br /&gt;
&lt;br /&gt;
* Basic webpage for advertising your events or just your society&lt;br /&gt;
* Websites with integration with [https://auth.bath.ac.uk auth.bath.ac.uk] to provide minimal account system (as long as it complies with GDPR).&lt;br /&gt;
* Provide graphs which track website performance&lt;br /&gt;
* Send emails from &amp;lt;code&amp;gt;bathcs.com&amp;lt;/code&amp;gt; domain, with reduced likelihood of going into spam.&lt;br /&gt;
* Websites with restricted user access (e.g. only committee members)&lt;br /&gt;
&lt;br /&gt;
Websites we have previously helped:&lt;br /&gt;
&lt;br /&gt;
* Bath Hack 24-26&lt;br /&gt;
* Witathon 25-26&lt;br /&gt;
&lt;br /&gt;
Although, we do have limited resources and so we can only build a few projects from scratch each year, but if you are somewhat technical and have already built a website, we are happy to help you host it.&lt;br /&gt;
&lt;br /&gt;
=== Future ===&lt;br /&gt;
BOSS tracks all of its potential future projects on its [https://gitlab.bath.ac.uk/cs/planning/ planning board], which any student can contribute to or discuss the projects.&lt;br /&gt;
&lt;br /&gt;
== Contributing ==&lt;br /&gt;
As a society, BOSS members can contribute to any project to help them improve and grow, there are many ways you can contribute, even without any technical knowledge. We track all our issues and store all our code on [https://gitlab.bath.ac.uk/cs Bath&#039;s GitLab instance], under the &amp;lt;code&amp;gt;cs&amp;lt;/code&amp;gt; organisation and all conversation is done through our hosted Matrix instance.&lt;br /&gt;
&lt;br /&gt;
=== Get Started ===&lt;br /&gt;
&amp;lt;!-- This needs to be written --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Tester/stakeholder ===&lt;br /&gt;
If you want to provide invaluable source of information about up and coming projects, you can get involved by joining our group chats and respond to questions around design. You can checkout current project ideas on our [https://gitlab.bath.ac.uk/cs/planning/ planning board].&lt;br /&gt;
&lt;br /&gt;
This makes sure our new projects are useful and relavent to you as a student.&lt;br /&gt;
&lt;br /&gt;
=== Bug reporter/Feature requester ===&lt;br /&gt;
If you find a bug or issue with a service we provide, or even just want to see a specific feature added, please see [[Creating a work item]]. This provides tasks for other students to complete and makes sure that our services are of the highest quality. Please be patient though, development takes time, especially when no one is paid to work on any specific feature.&lt;br /&gt;
&lt;br /&gt;
=== Designer ===&lt;br /&gt;
If you are good at art (unlike a lot of Computer Scientists), you can get involved by creating logos or even working with a developer to improve the style of our websites, through CSS and HTML. We are always looking to making our sites more intuitive and this is an invaluable source of help with that.&lt;br /&gt;
&lt;br /&gt;
=== Documenter ===&lt;br /&gt;
Computer scientists commonly struggle at writing long essays, though they love talking about their inventions. So you can help out by creating wiki pages about how to use a piece of software, or even read some code that is created and add documentation to the functions explaining what they do and how they work. As students leave every year, we need to make sure our code is as readable as possible.&lt;br /&gt;
&lt;br /&gt;
=== Developer ===&lt;br /&gt;
The core of our members, those who are just learning to code for a bit of fun, or wanting to get some practice before going to work at a real company. We work in a range of languages as we believe you should choose the right language for the job, but we tend to use Rust and TypeScript as they are the fun cool languages on the block.&lt;br /&gt;
&lt;br /&gt;
Before you contribute to any of our projects however, you must agree to our [https://boss.bathcs.com/policies/licenses/#developers-certificate-of-origin Developer Certificate of Origin] and understand our [https://boss.bathcs.com/policies/genai/ Generate AI Policy]. But you are free to fork any of our projects, make your changes and then submit an MR. If you are wanting to fix an issue, please send a message on that specific work item to state you are working on it, and make sure no one else is doing the same thing.&lt;br /&gt;
&lt;br /&gt;
If it is a large feature that is not an issue, please get in touch with the project owner or committee to discuss if it is wanted, as we do not want to waste your time.&lt;br /&gt;
&lt;br /&gt;
=== Project Owner ===&lt;br /&gt;
If you are assigned this role, you will be responsible for managing work items, reviewing merge requests and publishing releases of your projects. This role is not elected as we want the people with the most knowledge and understanding of the project.&lt;br /&gt;
&lt;br /&gt;
Project owners will be chosen each year and trained up before the previous owner leaves.&lt;br /&gt;
&lt;br /&gt;
=== Sysadmin ===&lt;br /&gt;
If you much prefer configuring systems, managing deployments and putting out fires (hopefully not literally), you can apply to become a system administrator. We aim to make this role as easy as possible, relying on automated deployments where ever possible. Although, many other factors can go wrong with 4 servers, and so we need people regularly checking in on them and making sure they have not been hacked or something.&lt;br /&gt;
&lt;br /&gt;
You will need specific knowledge of linux commands, ssh and maybe even kubernetes and FreeBSD (but that&#039;s generally easy to learn and we offer training). You will get varying levels of access to our production and staging kubernetes clusters as well as the machines that run them.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=156</id>
		<title>Kubernetes</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=156"/>
		<updated>2026-06-10T09:09:56Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* Deployment with Tofu */ Add note about configmaps requiring deployments to manually be restarted&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I ([[User:Hw2210|hw2210]]) have been asked to write down my processes for working with kubernetes and terraform as I am leaving this year, and hopefully it will serve as useful information to any future sysadmin. This page is the output of this and is written to be as generic as possible, so if you are just experimenting with kubernetes on a home lab, please feel free to read and hopefully you will learn somethings. There will be multiple references to [[BOSS/Hosting/Cluster|BOSS&#039;s cluster]], which has all the security boxes ticked on and so we have to deal with security contexts, network policies and [[SELinux]] as they are the bain of all problems.&lt;br /&gt;
&lt;br /&gt;
== General knowledge ==&lt;br /&gt;
This tries to cover some basic concepts, focusing on common confusion, but it will skip over a lot of the general knowledge information such as secrets and configmaps. The kubernete&#039;s documentation is pretty good, though difficult to read at some points, but there are loads of great tutorials explaining how kubernetes works.&lt;br /&gt;
&lt;br /&gt;
=== Pod vs Container ===&lt;br /&gt;
A common confusion is that [https://kubernetes.io/docs/concepts/workloads/pods/ pod]&#039;s are containers in kubernetes. This is not exactly true, a pod is a general group of linux namespaces which can host multiple containers. This means you can have a container that writes to a directory and another container that reads from that directory in the same pod. This can be very powerful, but in a lot of cases can be ignored.&lt;br /&gt;
&lt;br /&gt;
But it is key to point out that a Pod is a resource that is created by other kubernetes resources. They are a group of processes running, once they die the pod is deleted and forgotten about. Therefore you should not be creating pods directly, instead you should be using deployments, statefulsets, cronjobs or even jobs. All these resources create generate a pod as their lifecycle and will restart/recreate the pod if it fails.&lt;br /&gt;
&lt;br /&gt;
=== Statefulset vs deployment ===&lt;br /&gt;
Another key understanding is the difference between statefulsets and deployments, as statefulsets can cause some confusion in how they work. The difference is more applicable to multinode clusters but are still key to the structure of kubernetes.&lt;br /&gt;
&lt;br /&gt;
Effectively, a statefulset is a deployment with writable volumes - known as persistent volumes (PV). Having the ability to write to volumes can cause race conditions when multiple pods across nodes are writing to the same file. This is where statefulsets come in, they lock volumes and so they can only be used by one node and one pod, with scaling creating new persistant volumes which are stored separately. This means that if you scale a statefulset that relies on shared knowledge in the volume, half your requests will have one set of data and the other half will have another.&lt;br /&gt;
&lt;br /&gt;
This obviously is quite a big disadvantage and can lead to confusing behaviour when a node is not configured to shutdown safely and taint itself, moving all the statefulsets off of itself before it shutsdown - if PV is locked by a node and pod, it cannot be deployed to another cluster.&lt;br /&gt;
&lt;br /&gt;
Therefore, this is where deployments come in, they, usually, do not have associated persistent volumes, allowing for easy horizontal scaling. For storing shared data, they should connect to a database on another node which can be more compatible with statefulsets when configured correctly.&lt;br /&gt;
&lt;br /&gt;
Both of these resources will create pods and redeploy them if they crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=If you update any &amp;quot;volume&amp;quot; attribute within a statefulset ALL volumes will be deleted and recreated. To not lose any data, please make sure that the PV&#039;s reclaim policy has been set to &amp;quot;Retain&amp;quot;. You should generally do this for any data you do not want to lose.|type=warn}}&lt;br /&gt;
&lt;br /&gt;
==== Liveness/Startup probes ====&lt;br /&gt;
Liveness and startup probes can be defined on pods, and these let kubernetes know if a pod has started correctly and if it still is alive. For example, some deployments might take a while to start up and configure everything before it starts serving content and so when restarting, this can cause some downtime. Downtime is what we are trying to avoid and so by using a startup probe, kubernetes knows that this application is ready, and so it will only terminate the previous node once the new one is started up resulting in zero downtime!&lt;br /&gt;
&lt;br /&gt;
The liveness probe on the other hand periodically checks whether the pod is still alive. This means that if it suddenly stops responding due to a long database query, kubernetes can detect that and replace the pod with another further reducing downtime. However, this usually suggests something else is wrong with the application and so this should be investigated and fixed.&lt;br /&gt;
&lt;br /&gt;
==== Security Context ====&lt;br /&gt;
{{Note|text=Within [[BOSS/Hosting/Cluster|BOSS&#039;s kubernetes cluster]], we define a security policy which requires all pods to correctly define their security context and make sure that it is not running as root.}}&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ security context] defines what privileges the pod has when running, we effectively want this to be as minimal as possible to reduce attack surface area. E.g.&lt;br /&gt;
&lt;br /&gt;
* Run as user&lt;br /&gt;
* Don&#039;t allow privilege escalation&lt;br /&gt;
* Properly define seccomp policy&lt;br /&gt;
* Default SELinux container context&lt;br /&gt;
* Drop all capabilities&lt;br /&gt;
&lt;br /&gt;
However this can cause issues with third-party applications which commonly do some questionable things, e.g. require running as root or changing the uid. But for our pods you can mostly just copy and paste:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  # ...&lt;br /&gt;
  spec {&lt;br /&gt;
    # ...&lt;br /&gt;
    template {&lt;br /&gt;
      # ...&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          # ...&lt;br /&gt;
&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          # ...&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[Terraform]] for more information.&lt;br /&gt;
&lt;br /&gt;
=== What is a CRD? ===&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ Custom Resource Definition (CRD)], allows you to extend kubernetes capabilities and define custome resources. This is usually paired with an operator which reads the resources and performs some actions.&lt;br /&gt;
&lt;br /&gt;
We should never create our own, but third-party ones make it much easier for doing things such as creating ingress routes with traefik or define database clusters with our postgres operator.&lt;br /&gt;
&lt;br /&gt;
K9s and kubectl support these out of the box (as they are basically just schemas for yaml configuration), and you can see all pods by using the name of the resource.&lt;br /&gt;
&lt;br /&gt;
=== Traefik and gateways ===&lt;br /&gt;
[[File:Gateway diagram.svg|thumb|368x368px|A digram depicting the the flow of traffic from the internet, to traefik then to each namespace&#039;s gateway. Each gateway then looks at all connected certificates to add TLS authentication and then looks at the HTTPRoute to find which one matches based on the rules which then defines what service to forward the traffic to and subsequently the pods.]]&lt;br /&gt;
Kubernetes works by defining services, which give a common endpoint to call potentially multiple pods. These can then be exposed through HTTPRoutes and the [https://kubernetes.io/docs/concepts/services-networking/gateway/ Gateway API], in which traefik implements.&lt;br /&gt;
&lt;br /&gt;
The Gateway API resources are read by [https://doc.traefik.io/traefik/ traefik], which acts as the implementation, and acts accordingly to the defined configuration. Therefore, in essence, the gateways act only as a means for configuring traefik. But effectively, traefik has configured open ports it can expose, it then looks for Gateways, in the permitted namespaces, for their configuration. The gateways stores a list of ports that the namespace can expose (though it cannot add one that is not included within traefik configuration itself), as well as a list of certificates. At this point, the domain requested must have a certificate configured within the gateway, and all TLS logic is handled by traefik and so all further traefik is effectively decrypted. Notice here that if a certificate is not configured on the gateway, it cannot be served (one of the downsides of the gateway API).&lt;br /&gt;
&lt;br /&gt;
For us, we have decided to have each namespace have their own gateway, due to the protections traefik offers, this means that we do not have to do any cross namespace references for certificates, and do not have to update the main gateway anytime we need to add a certificate. There is an additional issue with this, is that during the time the certificate doesn&#039;t exist but is configured (e.g. when first request it), the gateway is deemed invalid and so doesn&#039;t route any traefik (even http). There is a plan to help mitigate this through the use of &amp;lt;code&amp;gt;ListenerSets&amp;lt;/code&amp;gt; but this is yet to be supported in traefik and still has this issue. Therefore, we want to make sure that a single gateway hosts services for as few applications as possible (preferably only one).&lt;br /&gt;
&lt;br /&gt;
Anyway, the gateway will have a number of child routes (&amp;lt;code&amp;gt;TLSRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HTTPRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GRPCRoutes&amp;lt;/code&amp;gt; and coming in the future &amp;lt;code&amp;gt;TCPRoute&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;UDPRoute&amp;lt;/code&amp;gt;). These routes act as queries to determine when and what traefik should forward to. So for example they act as:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
if hostname is example.bathcs.com forward to example-service&lt;br /&gt;
if the path starts with /api forward to api-service&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Then traefik can request those services, allowing kubernetes to effectively take over, looking at the pods associated with the service and using the defined algorithm to send the request to those pods and using the defined ports.&lt;br /&gt;
&lt;br /&gt;
This does mean there are a number of different places a port can change:&lt;br /&gt;
 Exposed port -&amp;gt; Traefik internal port -&amp;gt; Service port -&amp;gt; Pod port -&amp;gt; Application port&lt;br /&gt;
In most cases you should have the service port, pod port and application port all matching, this makes debugging a lot easier. Additionally there are few reasons why you will want to change traefik&#039;s internal port and the exposed port (but there are some!).&lt;br /&gt;
&lt;br /&gt;
==== Certificates ====&lt;br /&gt;
The thing with certificates is that we effectively never want to manually create them, the recommended expiry time for certificates is always dropping, with the most recent update at 45 days. This is way too much work for manual requesting and uploading and adds too many layers for it to go wrong. Therefore we use cert manager, which allows defining certificate objects within the cluster, cert manager will then go and do all the requesting for us and store it in a secret. Then it will also track the expiry and automatically update the certificate a week or so before it expires.&lt;br /&gt;
&lt;br /&gt;
There are multiple different methods it can use to validate that we are in fact in charge of the domain:&lt;br /&gt;
&lt;br /&gt;
* DNS - this is the preferred method, as it allows us generating certificates for protected IPs. But this requires a valid cloudflare API token (which is restricted to a single IP).&lt;br /&gt;
* HTTP - this is when the certificate authority will request our server from multiple locations, which means the DNS cannot be set to a protected IP. But it means we can generate certificates for domains that we don&#039;t control the DNS of (e.g. bath.ac.uk hostnames) but it is at least configured to point to our server. The integration with traefik means that there is no additional work required for the application to get these working.&lt;br /&gt;
* Cloudflare Origin - These are very special certificates and cannot be decrypted by the browser. The idea is that by generating these certificates, only cloudflare themselves will be able to decrypt the contents and so only they can proxy your IP. We use this specifically for [[Kubernetes#Cloudflare proxy|Cloudflare proxy]]-ing thought it doesn&#039;t provide us the true benefits (given our IP is still public)&lt;br /&gt;
&lt;br /&gt;
Within cert manager&#039;s speak, these are known as issuers, and we have cluster issuers defined for each (meaning any namespace in the cluster can use them).&lt;br /&gt;
{{Note|text=When cert manager is first requesting the certificate, the configured gateway will be invalid and so no routes attached will forward traffic.|type=reminder}}&lt;br /&gt;
&lt;br /&gt;
==== Cloudflare proxy ====&lt;br /&gt;
Cloudflare proxy offers the benefits of caching our content on &amp;quot;edge&amp;quot; servers, meaning that our websites perform much better on average as well as it can protect the IP of the machine, but as explained later we don&#039;t use cloudflare proxy everywhere and so lose this advantage. This caching is amazing when the application is configured for it to work well with it (e.g. correctly labelling requests as cachable). But it does not work with every application, especially third-party services which sometimes just break when using it. But it also adds troubling security questions, for example, a login page will also be proxied, and decrypted by cloudflare, resulting in cloudflare having access to all passwords that go through the site. For this reason we limit where we use cloudflare proxying to services that would benefit heavily from it (e.g. this Wiki as authentication is handled offsite).&lt;br /&gt;
&lt;br /&gt;
To setup cloudflare proxying, it is as simple as generating a certificate with the cloudflare origin issuer and exposing a HTTPRoute with the certificate and then enabling proxy in the dns record. Obviously this does not work with internal DNS records (e.g. &amp;lt;code&amp;gt;k8s.bathcs.com&amp;lt;/code&amp;gt;) and so our terraform config automatically detects and does not proxy this stuff.&lt;br /&gt;
&lt;br /&gt;
The cloudflare origin issuer then speaks to the cloudflare origin operator which requests a certificate from cloudflare themselves. The generated certificates can be found in the cloudflare dashboard for the domain under &amp;quot;SSL/TLS &amp;gt; Origin Server&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Network Policies ===&lt;br /&gt;
With additional security, comes additional policy managment. [https://kubernetes.io/docs/concepts/services-networking/network-policies/ Network policies] tell kubernetes where a pod is allows to send and receive traffic. However they are a bit confusing at times, and so can cause some headache when trying to debug why your pod cannot communicate with your database.&lt;br /&gt;
&lt;br /&gt;
* By default ALL outgoing traffic is allows.&lt;br /&gt;
* By default NO incoming traffic is allows (except from traefik).&lt;br /&gt;
&lt;br /&gt;
So by default, any pod is allows to communicate with any port on the internet, but not allows to communicate with any other pod in the whole cluster. In the futher we hope to disallow both by default, and so you will have to specify exactly what ports (and potentially where) your pod should be communicating.&lt;br /&gt;
&lt;br /&gt;
Within the terraform, we have helper functions built into the utilities for the databases to automatically generate network policies for incoming traffic, relying on the requirement of adding a label to your pod, they are usually outputted by the module under &amp;lt;code&amp;gt;client_labels&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
To define your own policy (in terraform ofc), there are two parts &amp;lt;code&amp;gt;ingress&amp;lt;/code&amp;gt; (incoming traffic) and &amp;lt;code&amp;gt;egress&amp;lt;/code&amp;gt; (outgoing traffic). Both of these can then be a list of rules matching pods that will be allows. They are additive, meaning that all network policies matching the pod will be combined to produce the final ruleset.&lt;br /&gt;
&lt;br /&gt;
The main rules you will want to focus on are:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;ip_block&amp;lt;/code&amp;gt; defines a lock of IPs where the traffic originates or is going to&lt;br /&gt;
* &amp;lt;code&amp;gt;namespaceSelector&amp;lt;/code&amp;gt; defines the labels matching on the namespaces where traffic is allows to/from&lt;br /&gt;
* &amp;lt;code&amp;gt;podSelector&amp;lt;/code&amp;gt; same as namespaces but specific to pods themselves (e.g. what our databases do)&lt;br /&gt;
* &amp;lt;code&amp;gt;ports&amp;lt;/code&amp;gt; defines the ports allows by these connections&lt;br /&gt;
&lt;br /&gt;
While defining any egress policies you must remember to include basic services, e.g. DNS and maybe NTP. An example full configuration might look like:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_network_policy_v1&amp;quot; &amp;quot;example&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;my-policy&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    egress {&lt;br /&gt;
      # Allow pod to use DNS&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 53&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 53&lt;br /&gt;
        protocol = &amp;quot;UDP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      # Allow pod to request its database&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 5432&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    ingress {&lt;br /&gt;
      # Allow pods within namespaces that have enabled ldap (with the defined&lt;br /&gt;
      # label) to request this pod via the &amp;quot;ldap&amp;quot; port&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = &amp;quot;ldap&amp;quot;&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      from {&lt;br /&gt;
        namespace_selector {&lt;br /&gt;
          match_labels = {&lt;br /&gt;
            allow_ldap = true&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    # This defines the pod that this network policy will be applied to.&lt;br /&gt;
    pod_selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = &amp;quot;affected-pod&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    # We have defined both Ingress and Egress rules for this pod&lt;br /&gt;
    policy_types = [&amp;quot;Ingress&amp;quot;, &amp;quot;Egress&amp;quot;]&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This shows an example configuration, allows the defined pod to communicate with a database (note that the database will also be required to have a network policy with an ingress rule allowing the pod to connect) and DNS and allow other pods in the cluster to communciate with it. Note that any http port is missing as traffic is automatically permitted to access any port within the cluster.&lt;br /&gt;
&lt;br /&gt;
=== Helm ===&lt;br /&gt;
[https://helm.sh/ Helm] is a tool which allows the deployment of a set of kubernetes resources from a single configuration. So for third party, complicated applications its amazing, and there are a lot hosted on [https://artifacthub.io/ artifacthub]  and other random places (as you can really easily host a helm repo for free). There are benefits, like being able to rollback a change to a previous version. However, there are a few things to note:&lt;br /&gt;
&lt;br /&gt;
* When configuring in terraform, do NOT add repos! The full url should go in the &amp;lt;code&amp;gt;repo&amp;lt;/code&amp;gt; config, or if its an oci url, it should go in the &amp;lt;code&amp;gt;chart&amp;lt;/code&amp;gt; attribute.&lt;br /&gt;
* Tofu will refuse to deploy a deploying helm chart (you have to rollback first)&lt;br /&gt;
* &#039;&#039;&#039;DO NOT STORE SECRETS IN VALUES&#039;&#039;&#039; - values are not stored securely, and so you should never store passwords or api keys directly in the values (this is an easy mistake to make when configuring), all previous sets of values will be stored forever in the cluster. Therefore, if you make this mistake, you will have to rotate the secret or delete the whole helm deployment and start again.&lt;br /&gt;
* Helm does not care if resources are changed between deployments - this is both good and bad, it means that you can apply &amp;quot;hacks&amp;quot; to helm charts you know will not change and they will not appear in the terraform plans to be fixed. But again this is not particularly good practice and can result in some confusing behaviour.&lt;br /&gt;
* When you delete a deployment, all persistant volumes will get wiped unless they have the &amp;quot;Retain&amp;quot; reclaim policy.&lt;br /&gt;
&lt;br /&gt;
Helm is a great tool for quickly deploying whole clusters of applications, but it should be used with caution, making sure the chart is reputable and well maintained. As we are using terraform for deployments, it should also only be used for third party applications.&lt;br /&gt;
&lt;br /&gt;
=== Persistant Volumes ===&lt;br /&gt;
[https://kubernetes.io/docs/concepts/storage/persistent-volumes/ Persistant volumes] (PVs) and persistant volume claims (PVCs) are one of the more convoluted things in kubernetes and one of the more dangerous as you are handling data.&lt;br /&gt;
&lt;br /&gt;
At a high level, you request storage by creating a persistant volume claim, the storage manager fullfills your claim by creating a persistant volume (which does not have an associated namespace) and then it is assigned to your pod. You should never be creating persistant volumes yourself, and probably want to be creating persistant volumes through the statefulset&#039;s template interface.&lt;br /&gt;
&lt;br /&gt;
For us, the storage operator is k3s&#039; built in one, but for clusters with multiple pods, it probably is going to be something like [https://longhorn.io/ longhorn], which manages keeping replicas of the storage on multiple machines. This brings up a core issue, a persistant volume can only be mounted to a pod on a node that stores the persistant volume, which is why longhorn is necessary on multi-node clusters and is why we decided to only have one node in each of our clusters.&lt;br /&gt;
&lt;br /&gt;
PVs have different types of [https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes access modes]: &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ReadOnlyMany&amp;lt;/code&amp;gt; which mostly control how many nodes can read or write to it at once (multiple pods can still mount it, as long as they are all on the same node). So for k3s&#039; storage class does not support &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ReadOnlyMany&amp;lt;/code&amp;gt;, so you should only be setting it to &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Reclaiming PVs is one of the big danger factors. By default a persistant volume will be deleted if there are no longer any claims for it, and the claims will probably get deleted (e.g. in helm and statefulsets). Therefore, if you have important data on a pod, you most likely want to set the [https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaim-policy reclaim policy], that means the volume cannot be automatically deleted, but it also means, if another PVC comes along and matches the PV, the PV will be assigned to the new claim and subsequently a new pod, which can cause a lot of confusion and headache. But it is more likely that the PVC is the redeployment of the original application and so you want it to be reassigned to the new PVC.&lt;br /&gt;
{{Note|text=If you delete an application, you MUST delete the PV manually when its reclaim policy is &amp;quot;Retain&amp;quot;, otherwise the data will never get deleted and could be reassigned to another claim.|type=warn}}&lt;br /&gt;
&lt;br /&gt;
===== Other volumes =====&lt;br /&gt;
Persistant volumes are probably the easiest to understand, but there are a lot of other types of volumes, most notably empty directorys and ephemoral volumes as well as volumes creating from config maps or secrets. All of these serve different purposes:&lt;br /&gt;
&lt;br /&gt;
* Empty directory - something like &amp;lt;code&amp;gt;/tmp&amp;lt;/code&amp;gt;, all data will be stored in RAM if set to writable and will be deleted when the pod restarts or is deleted. You should set a limit to how much RAM is allowed to be stored there, as it could soak up the full resources of the computer.&lt;br /&gt;
* [https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/ Emphemeral volumes] - similar to empty directorys, but instead of being stored in RAM, they are stored on disk (through persistant volumes) and will be deleted upon deletion of the pod and so mostly not really useful unless you are expecting to generate a lot of data during the running of the pod and don&#039;t want to retain them on reboot.&lt;br /&gt;
* Mounting secrets - this is read only storage and just allows you to mount configuration files that store database passwords or similar. Each key within the secret becomes a text file. Note that if its a binary file (e.g. image) you can use the &amp;lt;code&amp;gt;binaryData&amp;lt;/code&amp;gt; property and pass in a hex string.&lt;br /&gt;
* Mounting config maps - similar to secrets, but just are for files that don&#039;t have to be secret and encrypted on the machine.&lt;br /&gt;
&lt;br /&gt;
=== Cronjobs ===&lt;br /&gt;
&lt;br /&gt;
=== K3S ===&lt;br /&gt;
&lt;br /&gt;
== Deployment with Tofu ==&lt;br /&gt;
Information pertaining to terraform configuration itself can be found on [[Terraform|the Terraform page]] and for specific how to deploy to BOSS&#039; production and staging cluster, please see the [https://gitlab.bath.ac.uk/cs/int/terraform project&#039;s README] and [https://gitlab.bath.ac.uk/cs/boss/int-wiki internal wiki] for more information. This will mostly focus on the general process of deploying with tofu and the struggles.&lt;br /&gt;
&lt;br /&gt;
Please read the [[Kubernetes#K9s|K9s section]] for information about using k9s for monitoring deployments.&lt;br /&gt;
&lt;br /&gt;
Some resources may also have their own special command set to allow you to perform resource specific actions which is nice (these should be listed at the top right).&lt;br /&gt;
&lt;br /&gt;
Back to deployment, it&#039;s mostly:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu apply -var-file=./prod.tfvars [-target=module.something]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Using &amp;lt;code&amp;gt;-target&amp;lt;/code&amp;gt; can help a lot when the terraform module is huge as it will limit the number of resources it has to check for updates (though this means that you state and configuration can become desynced and so you should still do apply&#039;s without the targetting). You can even specify the exact resource you have just edited for super fast (for terraform) iterations.&lt;br /&gt;
&lt;br /&gt;
While it&#039;s deploying you cannot hit &amp;lt;code&amp;gt;Ctrl-c&amp;lt;/code&amp;gt; or well you can (twice) to force exit the application, but this can result in annoying consequencies:&lt;br /&gt;
&lt;br /&gt;
* Hitting it before you&#039;ve confirmed the plan (e.g. you accidentally forgot to include target and don&#039;t want to wait): The state &#039;&#039;&#039;WILL&#039;&#039;&#039; be locked and will not be unlocked. Therefore you have to run&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu force-unlock &amp;lt;uid&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where the &amp;lt;code&amp;gt;&amp;lt;uid&amp;gt;&amp;lt;/code&amp;gt; can be found by trying to run the apply command and it failing.&lt;br /&gt;
&lt;br /&gt;
* Hitting it while it&#039;s deploying new resources (e.g. the pods are not deploying and you didn&#039;t change the 5 minute timeout): on next run, tofu will try to recreate those resources, and so before running the command, you must open k9s, find the resources and delete them.&lt;br /&gt;
* Hitting it while updating helm charts (e.g. you didn&#039;t change the 10 minute timeout and it&#039;s not working): on the next run, tofu will refuse to deploy it, because the helm deployment is in an invalid state (&amp;quot;deploying&amp;quot;) and it will never exit this state. Therefore you must open up &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; type &amp;lt;code&amp;gt;helm &amp;lt;namespace&amp;gt;&amp;lt;/code&amp;gt;, wait a year, click &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; on the effected resource to list all the previous releases, go down to the last successful deployment and click &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; again to rollback to that release, and again wait a year while k9s refuses to respond.&lt;br /&gt;
&lt;br /&gt;
As you can probably tell deployment takes patience, expecially when you don&#039;t alter the timeouts in helm (which default to 10 minutes for creation and 10 minutes for deletion). You don&#039;t want to interrupt the flow while tofu is doing it&#039;s thing, so make sure you aren&#039;t going to have to wait 10 minutes because you made a simple typo causing the pods to crash loop.&lt;br /&gt;
&lt;br /&gt;
You can technically, temporarily edit the resources (with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; in k9s) to fix your mistake and to make it successfully deploy, so you can actually fix the mistake and redeploy (updating all the resources again) within a shorter time than it takes for tofu to timeout. You do want to be careful with timeouts however, as if you are on poor wifi (e.g. a train) or if the cluster is a bit pinned atm, deployments will take longer and the worst thing is if tofu times out but the deployment actually succeeded (though just reapply should update the state without an actualy redeploymenht unless its helm).&lt;br /&gt;
&lt;br /&gt;
{{Note|text=If updating config maps/secrets, you will have to manually restart affected pods for the changes to apply in some cases!}}&lt;br /&gt;
&lt;br /&gt;
=== Retaining PVs ===&lt;br /&gt;
As previously mentioned, whenever deploying new applications, you should mark any persistant volume that stores data you do not want accidentally deleted with a &amp;quot;Retain&amp;quot; reclaim policy. This can easily be done using &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; by typing &amp;lt;code&amp;gt;:pv&amp;lt;/code&amp;gt;, finding the pv attached to the claim, pressing &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; and searching for where the &amp;lt;code&amp;gt;reclaimPolicy&amp;lt;/code&amp;gt; is defined. By default it will be &amp;quot;Delete&amp;quot;, and you can just replace it with &amp;quot;Retain&amp;quot;. &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; will also show you a PV&#039;s reclaim policy on the list view which is nice to check if its already been done.&lt;br /&gt;
&lt;br /&gt;
=== Where is the state stored? ===&lt;br /&gt;
{{Note|text=State files store all passwords and secrets generated or collected by terraform unencrypted by default and so you should be extremely cautious with where the files are stored, who has access to them and never commit them.|type=warn}}&lt;br /&gt;
Usually, if you don&#039;t define any backends, it is stored in a &amp;lt;code&amp;gt;.tfstate&amp;lt;/code&amp;gt; file within the directory of your folder. If you are just managing your cluster with them, an easy place to store it is within the kubernetes cluster itself, using the kubernetes backend:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
terraform {&lt;br /&gt;
  # ...&lt;br /&gt;
&lt;br /&gt;
  backend &amp;quot;kubernetes&amp;quot; {&lt;br /&gt;
    secret_suffix  = &amp;quot;my-cluster&amp;quot;&lt;br /&gt;
    config_paths   = [var.kube_config_path]&lt;br /&gt;
    config_context = var.kube_context&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  # ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This is BOSS&#039;s method of storage as we do not have to trust the GitLab instance with our cluster&#039;s life. But others can be chosen, for example [https://docs.gitlab.com/user/infrastructure/iac/terraform_state/ within GitLab itself], although this should be done with extreme caution due to the risk of exposing plan files (which contain secrets) to the world.&lt;br /&gt;
&lt;br /&gt;
=== Managing the state ===&lt;br /&gt;
As we can never write perfect code the first time round, some MRs will result in refactorisation of components into new modules. Therefore state management is important. This annoyingly is painful especially with larger effected packages and so I would recommend considering the effects of destroying all the effected resources and reapplying them (which is what  &amp;lt;code&amp;gt;tofu apply ...&amp;lt;/code&amp;gt; will do by default). If you cannot afford this (e.g. with persistant volumes) you will have to use &amp;lt;code&amp;gt;tofu state mv&amp;lt;/code&amp;gt; which works by moving the specified address to another (make sure there is no typos though!&lt;br /&gt;
&lt;br /&gt;
However this requires the resource type to be the same through the move, which sometimes may not happen. At which point you&#039;ll want to delete the state information and re-import it. E.g.&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu state rm module.example.my_resource.name&lt;br /&gt;
# Import the module (Note: the format will differ depending on the provider)&lt;br /&gt;
tofu import module.example.module.refractorisation.my_new_resource.name my_resource/name&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Updating packages ===&lt;br /&gt;
To make upgrading easier, in submodules, packages are pinned to the nearest major version. You can then update the base package version and run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu init -upgrade&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Which will update the package versions on your machine and update the lock file.&lt;br /&gt;
&lt;br /&gt;
If you do not specify &amp;lt;code&amp;gt;-upgrade&amp;lt;/code&amp;gt; it will just update the module list (e.g. if you add a new application with a new use of &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== K9s ==&lt;br /&gt;
[https://k9scli.io/ k9s] is recommended for watching deployments and seeing why they failed as its generally just fantastic the more you get used to it. The common shortcuts you need to know are:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; will describe the currently selected object. If you go to the bottom (&amp;lt;code&amp;gt;Shift-G&amp;lt;/code&amp;gt;) you will be able to see events associated with that resource, for pods and statefulsets this is &#039;&#039;&#039;extremely useful&#039;&#039;&#039; as it includes failures for pulling images or security denials.&lt;br /&gt;
* &amp;lt;code&amp;gt;l&amp;lt;/code&amp;gt; see the logs of all containers associated with the resource.&lt;br /&gt;
* &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; while within a secret/configmap will show you the raw (string) data&lt;br /&gt;
* &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; to shell into a pod (note that some pods do not support this as they do not ship withsh&lt;br /&gt;
* &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; allows you to perform a rolling restart on a statefulset or deployment (meaning there should be no downtime if everything is configured). This is the recommended way to update a pod on production (&#039;&#039;&#039;do not&#039;&#039;&#039; just delete the pod itself).&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;enter&amp;gt;&amp;lt;/code&amp;gt; see any subresources (e.g. containers for pods, or pods for deployments/statefulsets)&lt;br /&gt;
* &amp;lt;code&amp;gt;Ctrl-d&amp;lt;/code&amp;gt; allows you to delete the resource&lt;br /&gt;
* &amp;lt;code&amp;gt;Ctrl-f&amp;lt;/code&amp;gt; allows you to add a port-forward to your own machine (really useful for debugging if an application is responding but is not available via traefik), or getting direct access to databases.&lt;br /&gt;
* &amp;lt;code&amp;gt;:&amp;lt;resource&amp;gt; &amp;lt;namespace|all&amp;gt;&amp;lt;/code&amp;gt; will change the list view to the given resource in the namespace&lt;br /&gt;
* &amp;lt;code&amp;gt;:&amp;lt;resource&amp;gt;&amp;lt;/code&amp;gt; will change the list view for the given resource in the currently selected namespace&lt;br /&gt;
* &amp;lt;code&amp;gt;0-9&amp;lt;/code&amp;gt; are shortcuts to switch between recently selected namespaces (they should show at the top with the current assignment)&lt;br /&gt;
&lt;br /&gt;
== Migrating storages ==&lt;br /&gt;
When you are migrating clusters to a new machine (just copy the VM though) or just moving statefulsets between namespaces (persistant volumes are namespace agnostic though), you may need to migrate persistant volumes. As I have done this multiple times now, and so here is my steps to do so.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=All pods using these volumes should be inaccessible during the process of this. This can be done by temporarily deleting any &amp;lt;code&amp;gt;HTTPRoute&amp;lt;/code&amp;gt;s to the service.}}&lt;br /&gt;
&lt;br /&gt;
=== Is it a database? ===&lt;br /&gt;
Databases have users and passwords associated, which can cause issues if you are redeploying a whole cluster with terraform as new passwords will be generated. You can import new values but that&#039;s boring. Instead you can just us &amp;lt;code&amp;gt;pg_dump&amp;lt;/code&amp;gt; (or equivalent):&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl port-forward -n my_namespace service/my_pod 5432:5432 &amp;amp;&lt;br /&gt;
pg_dump $DATABASE_URL &amp;gt; my_pod_bkp.sql&lt;br /&gt;
killall kubectl # Stop port-forward&lt;br /&gt;
# ...&lt;br /&gt;
# Apply the SQL to the volume in the new cluster:&lt;br /&gt;
kubectl --context new_cluster port-forward -n my_namespace service/my_port 5432:5432 &amp;amp;&lt;br /&gt;
psql -U username -d myDataBase -a -f my_pod_bkp.sql&lt;br /&gt;
killall kubectl # Stop port-forward&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Anything else ===&lt;br /&gt;
You will need to investigate permissions and such, but kubectl has this [https://kubernetes.io/docs/reference/kubectl/generated/kubectl_cp/ &amp;lt;code&amp;gt;cp&amp;lt;/code&amp;gt; command] which you can use to download whole directories. But before that you should run &amp;lt;code&amp;gt;ls -al&amp;lt;/code&amp;gt; while ssh-ed into the machine using the volume to view the permissions of the files.&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl cp my_namespace/pod:/where/is/pv/mounted bkp_dir&lt;br /&gt;
# ...&lt;br /&gt;
kubectl --context new_cluster cp bkp_dir my_namespace/pod:/where/is/pv/mounted&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;If the pod cannot run without the storage being set. You have two options: set it with basic data to be overridden, or create a dummy pod with the PV mounted and copy it to there.&lt;br /&gt;
&lt;br /&gt;
Remember to mark any new PVs that should retain data [[Kubernetes#Retaining PVs|with correct reclaim policy]].&lt;br /&gt;
&lt;br /&gt;
== Multinode clusters ==&lt;br /&gt;
[[Bath Open Source Society|BOSS]] has specifically chosen not to use multinode clusters and so this is just for the interested people, and hopefully explain why we did not go down this route. Yes, multinode clusters are the whole point of kubernetes, where the program chooses the at least somewhat optimal location to deploy an application and automatically move it to another node if the one its on crashes or restarts for an update. But there are also benefits of kubernetes outside of this feature, such as proper permission system as well as providing a somewhat clean interface for managing deployments.&lt;br /&gt;
&lt;br /&gt;
Additionally, we felt that as a lot of companies are moving towards managing deployments, at least partially, with kubernetes, our use of it will hopefully provide easy place for students to experiment and learn about it (as this is stuff university will never teach you). It should be noted that if companies actually cared about getting the most &amp;quot;bang for your buck&amp;quot;, it is worth more to manage deployments yourself and choose which nodes host them, though this can produce more overhead.&lt;br /&gt;
&lt;br /&gt;
=== Benefits ===&lt;br /&gt;
There are clear benefits of running a multinode cluster, node down? That&#039;s fine, all applications can still run just fine and are accessible. Updating your entrypoint? That&#039;s fine, just point your assumingly IPv4 to another node, or if you&#039;re using IPv6 just setup DNS to point to both nodes. With my multinode cluster, I am writing this currently after one node completely bricked itself and the other node down after I completely bricked it and all applications are still running fine on the three remaining nodes.&lt;br /&gt;
&lt;br /&gt;
=== Downsides ===&lt;br /&gt;
With all this new power comes significant overhead though. For one, if a node goes down its not always as simple as kubernetes automatically moving things. Firstly, volumes may only be stored on that one node, then they may only be locked to that one node with &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;. So if the machine didn&#039;t shutdown properly (or if one just get&#039;s disconnected from the control plane) you will have to make intervention steps as soon as possible, though, you don&#039;t have to be in a panic to get the node itself back up. If you have correctly [[Kubernetes#Safely shutting down nodes|setup safe shutdowns]] and its a routine update though, you probably don&#039;t need to touch it.&lt;br /&gt;
&lt;br /&gt;
Power, is another one, the more nodes, the more your [[Kubernetes#Control plane|control plane]] has to manage, meaning the more its going to be stuck managing the cluster, and so will not be able to run their own pods. This means that going from a single node cluster to a multinode cluster, you will probably need at least 3-4 additional machines for it to be managed and working properly. Yes you could run a 2 or 3 node cluster, but the benefits really are not there for the massive amount of downtime. On top of this, you probably want at least 1 node of wiggleroom capable of handling your highest power node, this means that you can never reach the full potential of your cluster, as both your control plane and your other nodes must be able to take the strain when that node is disconnected.&lt;br /&gt;
&lt;br /&gt;
Storage is probably the largest issue that you will need to tackle, and is the one that put BOSS of going this route (along with the fact that we wanted to keep some of our servers as hot spares if parts broke). This will be explored more in the [[Kubernetes#How to manage Storage|&amp;quot;How to manage Storage&amp;quot; section]], but the summary is, although there are tools out there, they require a minimum node count of 3 and have significant overhead. Additionally, because of the potential constant movement of data between nodes going down (even for general updates), it causes additional wear and tear on your drives, reducing their life expectancy (I killed a brand-new SSD in 6 months seemingly because my nodes were going down for updates so often). &lt;br /&gt;
&lt;br /&gt;
Networking is a minor one, all nodes need to be connected to control plane and cannot get randomly interrupted or cut off (e.g. you have your control plane in one room hooked up to a UPS and a agent node in another on a UPS, but the router that connects them is not on the UPS). Disconnection at critical times can cause the control node to start moving workloads off the disconnected node onto others, which with statefulsets or &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt; volumes can cause desynchronisation which is a massive issue and will cause corruption. Finally there is just a minor issue that if you don&#039;t setup correctly you will probably be quite confused, Traefik forwarding going between nodes may result in the final application being given the wrong IP address.&lt;br /&gt;
&lt;br /&gt;
Finally, there is also a bit of a weird one, managing SELinux policies and kernel modules across the cluster. I have had multiple issues in the past of minor configuration differences between my nodes resulting in a pod crashlooping on one node and absolutely just fine on another. Therefore this creates additional overhead as you then have to make sure all installs are identical and then potentially install the security profile operator (which is great when it works but not so great when the docker container they use is ancient and doesn&#039;t work on the latest systems and k3s accidentally breaks support for it). &lt;br /&gt;
&lt;br /&gt;
And so, in summary, multinode clusters are amazing for chaising that 99.99999999999% uptime and really interesting for learning the difficulties with it and all the solutions out there. But it is really not for the faint of heart, I would recommend trying with a bunch of VMs on a single node just to understand how it works, but if you are just a hobbiest wanting to host some basic applications at home, do not use it. I am planning on deconstructing my cluster and replacing it with a single node similar to [[Bath Open Source Society|BOSS]] after I leave univesity.&lt;br /&gt;
&lt;br /&gt;
=== Control plane ===&lt;br /&gt;
The first challenge is the control plane. What&#039;s the benefit of a multinode cluster when it relys on a single node for sending control signals. If it goes down, the whole cluster will panic if a pod crashes, it is left in a seriously vulnerable position. Therefore, you instead need to expand your control plane to multiple nodes. K3s offers built in solution for this, [https://docs.k3s.io/datastore/ha-embedded?_highlight=etcd utilising &amp;lt;code&amp;gt;etcd&amp;lt;/code&amp;gt;] by default to share the cluster state between control nodes, or you can just move it off to [https://docs.k3s.io/datastore/ha a separate database cluster] (which is what&#039;s recommended in production as you probably have another spare 3 machines). Then you must setup a load balancer between the control nodes (which actually helps with not overloading a single control node). But now the issue is that you are relying on a single load balancer. As a hacky solution, I setup a load balancer on all my control nodes which balanced the load between itself and the other nodes. This mean that the DNS record could store all IPs associated with the nodes and hopefully mitigate any communication issues when a single one went down.&lt;br /&gt;
&lt;br /&gt;
However, when setting up etcd, you must remember to point the &amp;lt;code&amp;gt;--server&amp;lt;/code&amp;gt; to the load balancer and not a single node (as then the single node becomes the single point of failure).&lt;br /&gt;
&lt;br /&gt;
=== How to manage Storage ===&lt;br /&gt;
I personally used [https://longhorn.io/ longhorn], which provided a somewhat easy solution, you just have to change the default storage class and migrate all your PVs. It requires that you have 3 nodes to share data between (as it can then use voting logic to ward off corruption). By default, it then stores volumes on three different nodes at the same time, meaning if one node goes down, it will just make a copy to a new node with the remaining two copies. Additionally, if a pod is assigned to a node without a copy of the data, longhorn can simply make a new copy onto that node (however it is more likely that kubernetes will choose a node that already has the storage).&lt;br /&gt;
&lt;br /&gt;
It then provides a UI where you can easily manually update all the storage volumes after an update of longhorn and also manage automated backups and snapshots. It is actually a really handy tool and generally really cool (though you have to manually label PVs with the types of backups you want).&lt;br /&gt;
&lt;br /&gt;
The issue? It uses around 2GiB of RAM on each individual node. Then if your nodes are constantly going down (e.g. automated updates every week), it will instantly start panicking and moving volumes over which can wear out drives. Additionally, if too many nodes go down, it can be the single source of crashing everything else because of the amount of resources it can use.&lt;br /&gt;
&lt;br /&gt;
=== Safely shutting down nodes ===&lt;br /&gt;
This is basically a requirement if you actually want to obtain the full benefits of multinode cluster that requires as little manual intervention as possible (which is still somehow more intervention than a single node cluster IMO). There is a great [https://oranki.net/posts/2025-01-09-graceful-k3s-shutdown/ blog on &amp;lt;code&amp;gt;oranki.net&amp;lt;/code&amp;gt;] on how to set this up, but effectively you need to set that anytime the &amp;lt;code&amp;gt;k3s&amp;lt;/code&amp;gt; service is stopped you run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl drain --ignore-daemonsets --delete-emptydir-data &amp;lt;node&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Then when it comes back up you need to run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl uncordon &amp;lt;node&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;As explained in the blog post, you can do this via a systemctl service.&lt;br /&gt;
&lt;br /&gt;
What this does is makes sure all nodes are safety migrated onto another node before a shutdown can take place, effectively tainting the node (meaning can be assigned to it). Then you must &amp;lt;code&amp;gt;uncordon&amp;lt;/code&amp;gt; a node (meaning removing the taint) to tell the control plane that you can now place pods on it again.&lt;br /&gt;
&lt;br /&gt;
== Backups ==&lt;br /&gt;
Within kubernetes, I&#039;d say there are 3 types of backups:&lt;br /&gt;
&lt;br /&gt;
* Whole OS/VM backups (really easy if host is running ZFS, but large)&lt;br /&gt;
* Resource configuration backup (saves time but if you are using terraform, not particularly useful): [https://velero.io/ velero]&lt;br /&gt;
* Persistant volume backup (small and application specific): built into some storage classes (e.g. [https://longhorn.io longhorn]) or you can use [https://k8up.io/ k8up]&lt;br /&gt;
&lt;br /&gt;
All of which you may want to use, however because I velero isn&#039;t amazing (It fails to back up PVs and the helm chart depends on paid pods, I prefer mixing a whole VM backup with ZFS snapshots and replication, and then do per volume backups with k8up for really important data to offsite cloud S3 bucket.&lt;br /&gt;
&lt;br /&gt;
== Alerting ==&lt;br /&gt;
Alerting is really important, for this I would simply just point to the [https://prometheus-operator.dev/ prometheus operator] or the [https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack kube-prometheus-stack], as these provide really easy methods to automatically monitor the clusters and send emails when something might be going wrong (but it can be extremely noisy) and node exporter requires additional configuration for SELinux enabled systems. This provides a nice standardised system that a lot of helm charts implement (and offer grafana boards you can import).&lt;br /&gt;
&lt;br /&gt;
When you get an email, you should investigate what the error actual means and what are the recommended steps to fix. E.g. if a job fails, you can either delete it or rerun it until it works again. For CPUThrottlingHigh errors, I still have absolutely no clue what you are supposed to do about it other than increasing the permitted resources for a pod (which isn&#039;t recommended).&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Kubernetes event log ===&lt;br /&gt;
&lt;br /&gt;
=== Traefik dashboard ===&lt;br /&gt;
&lt;br /&gt;
=== Shell-ing into pods ===&lt;br /&gt;
&lt;br /&gt;
=== K3S Log ===&lt;br /&gt;
&lt;br /&gt;
=== Emergency Debug pods ===&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare API tokens ===&lt;br /&gt;
&lt;br /&gt;
=== SELinux ===&lt;br /&gt;
&lt;br /&gt;
=== Tofu ===&lt;br /&gt;
&lt;br /&gt;
=== Scenarios ===&lt;br /&gt;
&lt;br /&gt;
==== Deployment/statefulset created but pod not creating ====&lt;br /&gt;
&lt;br /&gt;
==== Pod in cash loop ====&lt;br /&gt;
&lt;br /&gt;
==== Pod cannot request any website ====&lt;br /&gt;
&lt;br /&gt;
==== Pod can access the internet but everything returns self-signed certificate ====&lt;br /&gt;
&lt;br /&gt;
==== Deployment can&#039;t access its database/valkey ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is requesting denied system privileges ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 404 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 403 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Authelia refuses to start up ====&lt;br /&gt;
&lt;br /&gt;
==== Node is crashing often after running out of RAM ====&lt;br /&gt;
&lt;br /&gt;
==== On IPV6 cluster and requests randomly timeout or return 404 ====&lt;br /&gt;
&lt;br /&gt;
==== Statefulset refusing to start pod (PVC) ====&lt;br /&gt;
&lt;br /&gt;
==== A node just crashed and went offline ====&lt;br /&gt;
&lt;br /&gt;
=== Cleaning up ===&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/GitLab&amp;diff=150</id>
		<title>Git/GitLab</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/GitLab&amp;diff=150"/>
		<updated>2026-06-08T19:17:04Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Resize images&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;We require that the websites hosted on bathcs.com should be stored in [https://gitlab.bath.ac.uk/cs the cs organisation on Bath’s GitLab instance]. Any project used by student-led initiatives can also be stored on here.&lt;br /&gt;
&lt;br /&gt;
GitLab was chosen because it has a lot more (useful) features than GitHub. However this does mean that it is aimed at people who has a basic knowledge of how to properly manage git projects.&lt;br /&gt;
&lt;br /&gt;
Therefore you will need to be able to use GitLab.&lt;br /&gt;
&lt;br /&gt;
{{Note|type=warn|text=&lt;br /&gt;
You cannot push or pull to the GitLab outside of the VPN, even though you can access the page.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&lt;br /&gt;
You might have set this up when handing over, if you haven’t, here are the instructions. Note you will have to ask an organisation manager to add you to the organisation if you are going to be editing any current projects.&lt;br /&gt;
&lt;br /&gt;
* Go to https://gitlab.bath.ac.uk&lt;br /&gt;
* Login with your University credentials&lt;br /&gt;
* Setup TOTP as your 2FA (one time codes). See [[2FA#TOTP|our 2FA section]] for more information about what this is and how to add one (you can skip to the section where it says “Once you see a QR code”)&lt;br /&gt;
&lt;br /&gt;
Once you have logged in, your account will be created and so can be added to organisations or tagged (using @username).&lt;br /&gt;
&lt;br /&gt;
=== SSH Keys ===&lt;br /&gt;
&lt;br /&gt;
If you want to commit or clone repos, you will also need to setup an SSH key:&lt;br /&gt;
&lt;br /&gt;
# Create an SSH key: see [[SSH keys|our documentation]] for information on how to set one up.&lt;br /&gt;
# Go to your preferences (click profile icon then click preferences).&lt;br /&gt;
# Click “SSH Keys”.&lt;br /&gt;
# Copy and paste the public key into the “Key” section.&lt;br /&gt;
# Give it a title.&lt;br /&gt;
# Change usage type to “Authentication only”.&lt;br /&gt;
# Click “Add key”.&lt;br /&gt;
&lt;br /&gt;
By default this will expire in a year, after which you will have to add another key in the same way.&lt;br /&gt;
&lt;br /&gt;
=== Extra Emails ===&lt;br /&gt;
&lt;br /&gt;
You may have already set up git with your personal email address or you have a git signing key with a specific email. You probably don’t want to edit the configuration on a per project basis.&lt;br /&gt;
&lt;br /&gt;
To solve this, GitLab and GitHub allow you to have multiple email addresses linked to your account, this can be found by going to preferences (click the profile icon then preferences) then clicking “Emails”.&lt;br /&gt;
&lt;br /&gt;
You can then type in another email into the box and click “Add email address”. You will then have to verify the email by clicking the link it gives you.&lt;br /&gt;
&lt;br /&gt;
== The CS Organisation ==&lt;br /&gt;
&lt;br /&gt;
All Student Led Initiative projects are stored on the [https://gitlab.bath.ac.uk/cs Computer Science SLI organisation]. If you are a committee member you should have been given access to it. If not please contact the current owners, who can be found by going “Group information &amp;amp;gt; Members”.&lt;br /&gt;
&lt;br /&gt;
[[File:git-gitlab_members.gif|Members page on GitLab showing the current owners and maintainers found by going to Members in the navigation group “Group Information”|764x764px]]&lt;br /&gt;
&lt;br /&gt;
The organisation or group has subgroups for all societies or student-led initiatives. You should have developer rights on all initiatives which you are a part of, which allows you to create branches on the existing projects and push to them. If you ask nicely you may get maintainer rights, which will allow you to create new projects and merge MRs into main (however you still can’t push to the &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; branch).&lt;br /&gt;
&lt;br /&gt;
There are some projects which live outside the subgroups (e.g. this wiki) as they don’t have a organisation apart of. You will have to specifically ask permission to access individual projects for security reasons.&lt;br /&gt;
&lt;br /&gt;
== Issues ==&lt;br /&gt;
&lt;br /&gt;
=== Creating ===&lt;br /&gt;
&lt;br /&gt;
[[File:git-create_issue.gif|gif showing the steps of going to the issues menu under pinned and clicking new issue button|728x728px]]&lt;br /&gt;
&lt;br /&gt;
Creating an issue is easy, you can click on the “Issues” tab under “pinned” and click “New Issue” and start typing. The description is written in markdown. You can [https://docs.gitlab.com/ee/user/markdown.html learn markdown on the GitLab website].&lt;br /&gt;
&lt;br /&gt;
If the project is configured with templates, there should be a dropdown for you to choose the one most relavent to your issue type.&lt;br /&gt;
&lt;br /&gt;
[[File:git-mr_templates.gif|gif showing the dropdown to select the template to use above the description|491x491px]]&lt;br /&gt;
&lt;br /&gt;
You can then assign someone an issue from the menu if you know who will be working on it. Labels are also available, but these are mostly for the maintainers of the project to use.&lt;br /&gt;
&lt;br /&gt;
Once the issue is created, it will assign the issue a number (you can see this in the URL), which you can use to cross-reference issues or MRs by putting a &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; before it. GitLab will pick this up and turn it into a link (you can also just put the link and it will be sortened). Note that issues on other projects can also be referenced.&lt;br /&gt;
&lt;br /&gt;
=== Managing Issues ===&lt;br /&gt;
&lt;br /&gt;
There is not much to say about managing issues. All conversations about the issue should be done on the issue itself.&lt;br /&gt;
&lt;br /&gt;
Once someone is working on it, they should be assigned the issue and it should be referenced in the MR which fixes it.&lt;br /&gt;
&lt;br /&gt;
If “Closes” is put before the link to the issue on an MR, once it is merged, the issue should automatically be closed (but please always do check all the related issues are closed).&lt;br /&gt;
&lt;br /&gt;
== Forking + Branches ==&lt;br /&gt;
&lt;br /&gt;
If you are a developer or maintainer on a project, you should be able to create a branch on the project (but just not push directly main). Branches should be under the format &amp;lt;code&amp;gt;&amp;amp;lt;username&amp;amp;gt;/&amp;amp;lt;fix_summary&amp;amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
If you are not a developer but would like to contribute to a project, you should be able to fork the repository into your own space to create make any edits you wish.&lt;br /&gt;
&lt;br /&gt;
== Merge Requests ==&lt;br /&gt;
&lt;br /&gt;
=== Creating ===&lt;br /&gt;
&lt;br /&gt;
[[File:git-create_mr.gif|gif showing the steps of going to the merge requests menu under pinned and clicking new merge request button and choosing a branch to merge into main|820x820px]]&lt;br /&gt;
&lt;br /&gt;
Once you have created a fork or branch, you can create a merge request, either by clicking the link which is shown after pushing, or going onto the project and clicking “Merge requests” at the left and choosing “New merge request”. You can then choose the branch you want to merge from and the one you are merging to (most likely &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
The interface is very similar to issues, with the summary written in markdown and templates available.&lt;br /&gt;
&lt;br /&gt;
If the Merge Request is not finished, and so still a work in progress, you can click the “Mark Draft” button or put “DRAFT:” at the start of the summary.&lt;br /&gt;
&lt;br /&gt;
You can then assign the MR to yourself (as you will be managing it). You then also want to set the reviewer to a maintainer who can then merge the issue.&lt;br /&gt;
&lt;br /&gt;
If the MR depends on another MR to be merged beforehand, please add the “blocked” label and mention what its blocked on in a comment or the issue directly.&lt;br /&gt;
&lt;br /&gt;
=== Managing ===&lt;br /&gt;
&lt;br /&gt;
Sadly as the university does not pay for premium GitLab, we have to do most security practices as manual steps. So please do not go against these procedures, we beg of you.&lt;br /&gt;
&lt;br /&gt;
If you are the reviewer of a MR, you can go onto the “Changes” tab where you can see all the differences between this and the main branch. There is a settings button where you can change parts of the look. I recommend enabling “show whitespace changes” to help find where people have accidentally left trailing whitespace.&lt;br /&gt;
&lt;br /&gt;
You can then add a comment on a line by hovering over the line and clicking the comment button on the left hand side. You can also do this on images if you wish.&lt;br /&gt;
&lt;br /&gt;
When adding a comment you have the option to “start a review” which is recommended if you are going to be making multiple comments as it send them out all at once instead of having a separate email for each comment (which is a lot).&lt;br /&gt;
&lt;br /&gt;
If you drag the comment, you can also select multiple lines which is useful for segments of code which need changing.&lt;br /&gt;
&lt;br /&gt;
[[File:git-start_review.gif|gif showing going to commits tab and adding a comment to the review, selecting multiple lines|524x524px]]&lt;br /&gt;
&lt;br /&gt;
It is expected that the assignee will then resolve all of these issues so it is not up to the reviewer (but as we are students they may need chasing).&lt;br /&gt;
&lt;br /&gt;
Once you are happy with the MR, you can then approve the merge request before merging it.&lt;br /&gt;
&lt;br /&gt;
Please make sure that:&lt;br /&gt;
&lt;br /&gt;
* You don’t approve your own MR&lt;br /&gt;
* Don’t merge something without approval (if it is going into &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Don’t approve without actually reading all the changes&lt;br /&gt;
&lt;br /&gt;
== Creating a new Project ==&lt;br /&gt;
&lt;br /&gt;
Over time we may want to add more projects to the organisation. This page focuses on creating projects from scratch. If you are just wanting to mirror, see the [[Git/Mirrors|page about mirrors]].&lt;br /&gt;
&lt;br /&gt;
When creating a new repo or project, you need to decide which subgroup it should go under. This should be quite simple, however you may decide a new subgroup is required.&lt;br /&gt;
&lt;br /&gt;
If you are not a maintainer in a repository, you will have to find the maintainers of the subgroup and ask one of them to set up the repo for you and then add you to the access list.&lt;br /&gt;
&lt;br /&gt;
Other things to consider when creating a project:&lt;br /&gt;
&lt;br /&gt;
* Name (please make this understandable about what it is for)&lt;br /&gt;
* Visibility level:&lt;br /&gt;
** &#039;&#039;&#039;Private&#039;&#039;&#039;: only people with access can see it (preferably not), however is understandable if you are just testing out a project.&lt;br /&gt;
** &#039;&#039;&#039;Internal&#039;&#039;&#039;: Only bath students can see it when logged in, this is useful for things which interact with University of Bath services which they may not want known to the public.&lt;br /&gt;
** &#039;&#039;&#039;Public&#039;&#039;&#039;: This should be used for most projects to encourage the open source nature of the community.&lt;br /&gt;
&lt;br /&gt;
Once a repository is created you can add it as a remote to your repo or clone it.&lt;br /&gt;
&lt;br /&gt;
You should then:&lt;br /&gt;
&lt;br /&gt;
* Go to “Settings &amp;amp;gt; Repository &amp;amp;gt; Protected branches” and change “Allowed to push and merge” to be “No one” and make sure “Merge MRs” is set to “Maintainers only”.&lt;br /&gt;
* Add a description or logo in the “Settings &amp;amp;gt; General” tab&lt;br /&gt;
* Add a license as explained [[Git/Special files#license|here]].&lt;br /&gt;
&lt;br /&gt;
[[File:git-setup_new_project.gif|gif showing the process of editing the protected branches to only allow maintainers to merge to main and no one to push to the main|627x627px]]&lt;br /&gt;
&lt;br /&gt;
=== Manage Access ===&lt;br /&gt;
&lt;br /&gt;
You can choose who can access the repository in “Manage &amp;amp;gt; Members” if you are a Maintainer or Owner.&lt;br /&gt;
&lt;br /&gt;
To add new person you can click “Invite members” and type in their username (they will need to have signed into GitLab for their username to appear).&lt;br /&gt;
&lt;br /&gt;
You then need to choose their role. It is recommended to put them as “Developer” unless you want them to be able to merge requests into &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;, then put them as “Maintainer”.&lt;br /&gt;
&lt;br /&gt;
=== Labels ===&lt;br /&gt;
&lt;br /&gt;
Under “Manage &amp;amp;gt; Labels” you can add or create labels which can be used on merge requests or issues. You should inherit any labels from the parent group, and preferably labels should be group wide. But you can create a label and ask an Owner to upgrade the label to a group label by clicking the 3 dots.&lt;br /&gt;
&lt;br /&gt;
=== Issue + MR Templates ===&lt;br /&gt;
&lt;br /&gt;
In your project, you may want to have templates for people creating issues or MRs to save them time and to standardise them.&lt;br /&gt;
&lt;br /&gt;
This is quite simple to do in GitLab as explained [https://docs.gitlab.com/ee/user/project/description_templates.html here].&lt;br /&gt;
&lt;br /&gt;
In short, you need to add markdown files to &amp;lt;code&amp;gt;.gitlab/issue_templates/&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;.gitlab/merge_request_templates/&amp;lt;/code&amp;gt;. The default should be named &amp;lt;code&amp;gt;default.md&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;.gitlab/issue_templates/default.md&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;### Summary&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!--Add brief summary explaining the issue--&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
### Steps to reproduce&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Add brief instructions to reproduce the issue --&amp;amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;.gitlab/merge_request_templates/default.md&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;### Related Issue&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!--Replace with the related issue number--&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Closes #&lt;br /&gt;
&lt;br /&gt;
### Summary&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Summary of what the MR does --&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
### Testing&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Summary of the testing done to validate the MR --&amp;amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;.gitlab/merge_request_templates/bug_fix.md&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;### Summary&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Summary of what the the bug was --&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
### Testing&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Summary of the testing done to validate the MR --&amp;amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CI Pipelines ===&lt;br /&gt;
&lt;br /&gt;
GitLab pipelines are super powerful and I don’t think I could write better documentation than GitLab themselves (which can be found [https://docs.gitlab.com/ee/ci/pipelines/ here]).&lt;br /&gt;
&lt;br /&gt;
This is just here to say they do exist and they should be used for automated testing or even deployment.&lt;br /&gt;
&lt;br /&gt;
The configuration can be found in the &amp;lt;code&amp;gt;.gitlab-ci.yml&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git|namespace=0|hideredirects=1}}&lt;br /&gt;
[[Category:Tutorial]]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=147</id>
		<title>Kubernetes</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=147"/>
		<updated>2026-06-07T13:22:10Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Add info about migration, backups and alerting&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I ([[User:Hw2210|hw2210]]) have been asked to write down my processes for working with kubernetes and terraform as I am leaving this year, and hopefully it will serve as useful information to any future sysadmin. This page is the output of this and is written to be as generic as possible, so if you are just experimenting with kubernetes on a home lab, please feel free to read and hopefully you will learn somethings. There will be multiple references to [[BOSS/Hosting/Cluster|BOSS&#039;s cluster]], which has all the security boxes ticked on and so we have to deal with security contexts, network policies and [[SELinux]] as they are the bain of all problems.&lt;br /&gt;
&lt;br /&gt;
== General knowledge ==&lt;br /&gt;
This tries to cover some basic concepts, focusing on common confusion, but it will skip over a lot of the general knowledge information such as secrets and configmaps. The kubernete&#039;s documentation is pretty good, though difficult to read at some points, but there are loads of great tutorials explaining how kubernetes works.&lt;br /&gt;
&lt;br /&gt;
=== Pod vs Container ===&lt;br /&gt;
A common confusion is that [https://kubernetes.io/docs/concepts/workloads/pods/ pod]&#039;s are containers in kubernetes. This is not exactly true, a pod is a general group of linux namespaces which can host multiple containers. This means you can have a container that writes to a directory and another container that reads from that directory in the same pod. This can be very powerful, but in a lot of cases can be ignored.&lt;br /&gt;
&lt;br /&gt;
But it is key to point out that a Pod is a resource that is created by other kubernetes resources. They are a group of processes running, once they die the pod is deleted and forgotten about. Therefore you should not be creating pods directly, instead you should be using deployments, statefulsets, cronjobs or even jobs. All these resources create generate a pod as their lifecycle and will restart/recreate the pod if it fails.&lt;br /&gt;
&lt;br /&gt;
=== Statefulset vs deployment ===&lt;br /&gt;
Another key understanding is the difference between statefulsets and deployments, as statefulsets can cause some confusion in how they work. The difference is more applicable to multinode clusters but are still key to the structure of kubernetes.&lt;br /&gt;
&lt;br /&gt;
Effectively, a statefulset is a deployment with writable volumes - known as persistent volumes (PV). Having the ability to write to volumes can cause race conditions when multiple pods across nodes are writing to the same file. This is where statefulsets come in, they lock volumes and so they can only be used by one node and one pod, with scaling creating new persistant volumes which are stored separately. This means that if you scale a statefulset that relies on shared knowledge in the volume, half your requests will have one set of data and the other half will have another.&lt;br /&gt;
&lt;br /&gt;
This obviously is quite a big disadvantage and can lead to confusing behaviour when a node is not configured to shutdown safely and taint itself, moving all the statefulsets off of itself before it shutsdown - if PV is locked by a node and pod, it cannot be deployed to another cluster.&lt;br /&gt;
&lt;br /&gt;
Therefore, this is where deployments come in, they, usually, do not have associated persistent volumes, allowing for easy horizontal scaling. For storing shared data, they should connect to a database on another node which can be more compatible with statefulsets when configured correctly.&lt;br /&gt;
&lt;br /&gt;
Both of these resources will create pods and redeploy them if they crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=If you update any &amp;quot;volume&amp;quot; attribute within a statefulset ALL volumes will be deleted and recreated. To not lose any data, please make sure that the PV&#039;s reclaim policy has been set to &amp;quot;Retain&amp;quot;. You should generally do this for any data you do not want to lose.|type=warn}}&lt;br /&gt;
&lt;br /&gt;
==== Liveness/Startup probes ====&lt;br /&gt;
Liveness and startup probes can be defined on pods, and these let kubernetes know if a pod has started correctly and if it still is alive. For example, some deployments might take a while to start up and configure everything before it starts serving content and so when restarting, this can cause some downtime. Downtime is what we are trying to avoid and so by using a startup probe, kubernetes knows that this application is ready, and so it will only terminate the previous node once the new one is started up resulting in zero downtime!&lt;br /&gt;
&lt;br /&gt;
The liveness probe on the other hand periodically checks whether the pod is still alive. This means that if it suddenly stops responding due to a long database query, kubernetes can detect that and replace the pod with another further reducing downtime. However, this usually suggests something else is wrong with the application and so this should be investigated and fixed.&lt;br /&gt;
&lt;br /&gt;
==== Security Context ====&lt;br /&gt;
{{Note|text=Within [[BOSS/Hosting/Cluster|BOSS&#039;s kubernetes cluster]], we define a security policy which requires all pods to correctly define their security context and make sure that it is not running as root.}}&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ security context] defines what privileges the pod has when running, we effectively want this to be as minimal as possible to reduce attack surface area. E.g.&lt;br /&gt;
&lt;br /&gt;
* Run as user&lt;br /&gt;
* Don&#039;t allow privilege escalation&lt;br /&gt;
* Properly define seccomp policy&lt;br /&gt;
* Default SELinux container context&lt;br /&gt;
* Drop all capabilities&lt;br /&gt;
&lt;br /&gt;
However this can cause issues with third-party applications which commonly do some questionable things, e.g. require running as root or changing the uid. But for our pods you can mostly just copy and paste:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  # ...&lt;br /&gt;
  spec {&lt;br /&gt;
    # ...&lt;br /&gt;
    template {&lt;br /&gt;
      # ...&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          # ...&lt;br /&gt;
&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          # ...&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[Terraform]] for more information.&lt;br /&gt;
&lt;br /&gt;
=== What is a CRD? ===&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ Custom Resource Definition (CRD)], allows you to extend kubernetes capabilities and define custome resources. This is usually paired with an operator which reads the resources and performs some actions.&lt;br /&gt;
&lt;br /&gt;
We should never create our own, but third-party ones make it much easier for doing things such as creating ingress routes with traefik or define database clusters with our postgres operator.&lt;br /&gt;
&lt;br /&gt;
K9s and kubectl support these out of the box (as they are basically just schemas for yaml configuration), and you can see all pods by using the name of the resource.&lt;br /&gt;
&lt;br /&gt;
=== Traefik and gateways ===&lt;br /&gt;
[[File:Gateway diagram.svg|thumb|368x368px|A digram depicting the the flow of traffic from the internet, to traefik then to each namespace&#039;s gateway. Each gateway then looks at all connected certificates to add TLS authentication and then looks at the HTTPRoute to find which one matches based on the rules which then defines what service to forward the traffic to and subsequently the pods.]]&lt;br /&gt;
Kubernetes works by defining services, which give a common endpoint to call potentially multiple pods. These can then be exposed through HTTPRoutes and the [https://kubernetes.io/docs/concepts/services-networking/gateway/ Gateway API], in which traefik implements.&lt;br /&gt;
&lt;br /&gt;
The Gateway API resources are read by [https://doc.traefik.io/traefik/ traefik], which acts as the implementation, and acts accordingly to the defined configuration. Therefore, in essence, the gateways act only as a means for configuring traefik. But effectively, traefik has configured open ports it can expose, it then looks for Gateways, in the permitted namespaces, for their configuration. The gateways stores a list of ports that the namespace can expose (though it cannot add one that is not included within traefik configuration itself), as well as a list of certificates. At this point, the domain requested must have a certificate configured within the gateway, and all TLS logic is handled by traefik and so all further traefik is effectively decrypted. Notice here that if a certificate is not configured on the gateway, it cannot be served (one of the downsides of the gateway API).&lt;br /&gt;
&lt;br /&gt;
For us, we have decided to have each namespace have their own gateway, due to the protections traefik offers, this means that we do not have to do any cross namespace references for certificates, and do not have to update the main gateway anytime we need to add a certificate. There is an additional issue with this, is that during the time the certificate doesn&#039;t exist but is configured (e.g. when first request it), the gateway is deemed invalid and so doesn&#039;t route any traefik (even http). There is a plan to help mitigate this through the use of &amp;lt;code&amp;gt;ListenerSets&amp;lt;/code&amp;gt; but this is yet to be supported in traefik and still has this issue. Therefore, we want to make sure that a single gateway hosts services for as few applications as possible (preferably only one).&lt;br /&gt;
&lt;br /&gt;
Anyway, the gateway will have a number of child routes (&amp;lt;code&amp;gt;TLSRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HTTPRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GRPCRoutes&amp;lt;/code&amp;gt; and coming in the future &amp;lt;code&amp;gt;TCPRoute&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;UDPRoute&amp;lt;/code&amp;gt;). These routes act as queries to determine when and what traefik should forward to. So for example they act as:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
if hostname is example.bathcs.com forward to example-service&lt;br /&gt;
if the path starts with /api forward to api-service&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Then traefik can request those services, allowing kubernetes to effectively take over, looking at the pods associated with the service and using the defined algorithm to send the request to those pods and using the defined ports.&lt;br /&gt;
&lt;br /&gt;
This does mean there are a number of different places a port can change:&lt;br /&gt;
 Exposed port -&amp;gt; Traefik internal port -&amp;gt; Service port -&amp;gt; Pod port -&amp;gt; Application port&lt;br /&gt;
In most cases you should have the service port, pod port and application port all matching, this makes debugging a lot easier. Additionally there are few reasons why you will want to change traefik&#039;s internal port and the exposed port (but there are some!).&lt;br /&gt;
&lt;br /&gt;
==== Certificates ====&lt;br /&gt;
The thing with certificates is that we effectively never want to manually create them, the recommended expiry time for certificates is always dropping, with the most recent update at 45 days. This is way too much work for manual requesting and uploading and adds too many layers for it to go wrong. Therefore we use cert manager, which allows defining certificate objects within the cluster, cert manager will then go and do all the requesting for us and store it in a secret. Then it will also track the expiry and automatically update the certificate a week or so before it expires.&lt;br /&gt;
&lt;br /&gt;
There are multiple different methods it can use to validate that we are in fact in charge of the domain:&lt;br /&gt;
&lt;br /&gt;
* DNS - this is the preferred method, as it allows us generating certificates for protected IPs. But this requires a valid cloudflare API token (which is restricted to a single IP).&lt;br /&gt;
* HTTP - this is when the certificate authority will request our server from multiple locations, which means the DNS cannot be set to a protected IP. But it means we can generate certificates for domains that we don&#039;t control the DNS of (e.g. bath.ac.uk hostnames) but it is at least configured to point to our server. The integration with traefik means that there is no additional work required for the application to get these working.&lt;br /&gt;
* Cloudflare Origin - These are very special certificates and cannot be decrypted by the browser. The idea is that by generating these certificates, only cloudflare themselves will be able to decrypt the contents and so only they can proxy your IP. We use this specifically for [[Kubernetes#Cloudflare proxy|Cloudflare proxy]]-ing thought it doesn&#039;t provide us the true benefits (given our IP is still public)&lt;br /&gt;
&lt;br /&gt;
Within cert manager&#039;s speak, these are known as issuers, and we have cluster issuers defined for each (meaning any namespace in the cluster can use them).&lt;br /&gt;
{{Note|text=When cert manager is first requesting the certificate, the configured gateway will be invalid and so no routes attached will forward traffic.|type=reminder}}&lt;br /&gt;
&lt;br /&gt;
==== Cloudflare proxy ====&lt;br /&gt;
Cloudflare proxy offers the benefits of caching our content on &amp;quot;edge&amp;quot; servers, meaning that our websites perform much better on average as well as it can protect the IP of the machine, but as explained later we don&#039;t use cloudflare proxy everywhere and so lose this advantage. This caching is amazing when the application is configured for it to work well with it (e.g. correctly labelling requests as cachable). But it does not work with every application, especially third-party services which sometimes just break when using it. But it also adds troubling security questions, for example, a login page will also be proxied, and decrypted by cloudflare, resulting in cloudflare having access to all passwords that go through the site. For this reason we limit where we use cloudflare proxying to services that would benefit heavily from it (e.g. this Wiki as authentication is handled offsite).&lt;br /&gt;
&lt;br /&gt;
To setup cloudflare proxying, it is as simple as generating a certificate with the cloudflare origin issuer and exposing a HTTPRoute with the certificate and then enabling proxy in the dns record. Obviously this does not work with internal DNS records (e.g. &amp;lt;code&amp;gt;k8s.bathcs.com&amp;lt;/code&amp;gt;) and so our terraform config automatically detects and does not proxy this stuff.&lt;br /&gt;
&lt;br /&gt;
The cloudflare origin issuer then speaks to the cloudflare origin operator which requests a certificate from cloudflare themselves. The generated certificates can be found in the cloudflare dashboard for the domain under &amp;quot;SSL/TLS &amp;gt; Origin Server&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Network Policies ===&lt;br /&gt;
With additional security, comes additional policy managment. [https://kubernetes.io/docs/concepts/services-networking/network-policies/ Network policies] tell kubernetes where a pod is allows to send and receive traffic. However they are a bit confusing at times, and so can cause some headache when trying to debug why your pod cannot communicate with your database.&lt;br /&gt;
&lt;br /&gt;
* By default ALL outgoing traffic is allows.&lt;br /&gt;
* By default NO incoming traffic is allows (except from traefik).&lt;br /&gt;
&lt;br /&gt;
So by default, any pod is allows to communicate with any port on the internet, but not allows to communicate with any other pod in the whole cluster. In the futher we hope to disallow both by default, and so you will have to specify exactly what ports (and potentially where) your pod should be communicating.&lt;br /&gt;
&lt;br /&gt;
Within the terraform, we have helper functions built into the utilities for the databases to automatically generate network policies for incoming traffic, relying on the requirement of adding a label to your pod, they are usually outputted by the module under &amp;lt;code&amp;gt;client_labels&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
To define your own policy (in terraform ofc), there are two parts &amp;lt;code&amp;gt;ingress&amp;lt;/code&amp;gt; (incoming traffic) and &amp;lt;code&amp;gt;egress&amp;lt;/code&amp;gt; (outgoing traffic). Both of these can then be a list of rules matching pods that will be allows. They are additive, meaning that all network policies matching the pod will be combined to produce the final ruleset.&lt;br /&gt;
&lt;br /&gt;
The main rules you will want to focus on are:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;ip_block&amp;lt;/code&amp;gt; defines a lock of IPs where the traffic originates or is going to&lt;br /&gt;
* &amp;lt;code&amp;gt;namespaceSelector&amp;lt;/code&amp;gt; defines the labels matching on the namespaces where traffic is allows to/from&lt;br /&gt;
* &amp;lt;code&amp;gt;podSelector&amp;lt;/code&amp;gt; same as namespaces but specific to pods themselves (e.g. what our databases do)&lt;br /&gt;
* &amp;lt;code&amp;gt;ports&amp;lt;/code&amp;gt; defines the ports allows by these connections&lt;br /&gt;
&lt;br /&gt;
While defining any egress policies you must remember to include basic services, e.g. DNS and maybe NTP. An example full configuration might look like:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_network_policy_v1&amp;quot; &amp;quot;example&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;my-policy&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    egress {&lt;br /&gt;
      # Allow pod to use DNS&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 53&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 53&lt;br /&gt;
        protocol = &amp;quot;UDP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      # Allow pod to request its database&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 5432&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    ingress {&lt;br /&gt;
      # Allow pods within namespaces that have enabled ldap (with the defined&lt;br /&gt;
      # label) to request this pod via the &amp;quot;ldap&amp;quot; port&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = &amp;quot;ldap&amp;quot;&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      from {&lt;br /&gt;
        namespace_selector {&lt;br /&gt;
          match_labels = {&lt;br /&gt;
            allow_ldap = true&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    # This defines the pod that this network policy will be applied to.&lt;br /&gt;
    pod_selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = &amp;quot;affected-pod&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    # We have defined both Ingress and Egress rules for this pod&lt;br /&gt;
    policy_types = [&amp;quot;Ingress&amp;quot;, &amp;quot;Egress&amp;quot;]&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This shows an example configuration, allows the defined pod to communicate with a database (note that the database will also be required to have a network policy with an ingress rule allowing the pod to connect) and DNS and allow other pods in the cluster to communciate with it. Note that any http port is missing as traffic is automatically permitted to access any port within the cluster.&lt;br /&gt;
&lt;br /&gt;
=== Helm ===&lt;br /&gt;
[https://helm.sh/ Helm] is a tool which allows the deployment of a set of kubernetes resources from a single configuration. So for third party, complicated applications its amazing, and there are a lot hosted on [https://artifacthub.io/ artifacthub]  and other random places (as you can really easily host a helm repo for free). There are benefits, like being able to rollback a change to a previous version. However, there are a few things to note:&lt;br /&gt;
&lt;br /&gt;
* When configuring in terraform, do NOT add repos! The full url should go in the &amp;lt;code&amp;gt;repo&amp;lt;/code&amp;gt; config, or if its an oci url, it should go in the &amp;lt;code&amp;gt;chart&amp;lt;/code&amp;gt; attribute.&lt;br /&gt;
* Tofu will refuse to deploy a deploying helm chart (you have to rollback first)&lt;br /&gt;
* &#039;&#039;&#039;DO NOT STORE SECRETS IN VALUES&#039;&#039;&#039; - values are not stored securely, and so you should never store passwords or api keys directly in the values (this is an easy mistake to make when configuring), all previous sets of values will be stored forever in the cluster. Therefore, if you make this mistake, you will have to rotate the secret or delete the whole helm deployment and start again.&lt;br /&gt;
* Helm does not care if resources are changed between deployments - this is both good and bad, it means that you can apply &amp;quot;hacks&amp;quot; to helm charts you know will not change and they will not appear in the terraform plans to be fixed. But again this is not particularly good practice and can result in some confusing behaviour.&lt;br /&gt;
* When you delete a deployment, all persistant volumes will get wiped unless they have the &amp;quot;Retain&amp;quot; reclaim policy.&lt;br /&gt;
&lt;br /&gt;
Helm is a great tool for quickly deploying whole clusters of applications, but it should be used with caution, making sure the chart is reputable and well maintained. As we are using terraform for deployments, it should also only be used for third party applications.&lt;br /&gt;
&lt;br /&gt;
=== Persistant Volumes ===&lt;br /&gt;
[https://kubernetes.io/docs/concepts/storage/persistent-volumes/ Persistant volumes] (PVs) and persistant volume claims (PVCs) are one of the more convoluted things in kubernetes and one of the more dangerous as you are handling data.&lt;br /&gt;
&lt;br /&gt;
At a high level, you request storage by creating a persistant volume claim, the storage manager fullfills your claim by creating a persistant volume (which does not have an associated namespace) and then it is assigned to your pod. You should never be creating persistant volumes yourself, and probably want to be creating persistant volumes through the statefulset&#039;s template interface.&lt;br /&gt;
&lt;br /&gt;
For us, the storage operator is k3s&#039; built in one, but for clusters with multiple pods, it probably is going to be something like [https://longhorn.io/ longhorn], which manages keeping replicas of the storage on multiple machines. This brings up a core issue, a persistant volume can only be mounted to a pod on a node that stores the persistant volume, which is why longhorn is necessary on multi-node clusters and is why we decided to only have one node in each of our clusters.&lt;br /&gt;
&lt;br /&gt;
PVs have different types of [https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes access modes]: &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ReadOnlyMany&amp;lt;/code&amp;gt; which mostly control how many nodes can read or write to it at once (multiple pods can still mount it, as long as they are all on the same node). So for k3s&#039; storage class does not support &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ReadOnlyMany&amp;lt;/code&amp;gt;, so you should only be setting it to &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Reclaiming PVs is one of the big danger factors. By default a persistant volume will be deleted if there are no longer any claims for it, and the claims will probably get deleted (e.g. in helm and statefulsets). Therefore, if you have important data on a pod, you most likely want to set the [https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaim-policy reclaim policy], that means the volume cannot be automatically deleted, but it also means, if another PVC comes along and matches the PV, the PV will be assigned to the new claim and subsequently a new pod, which can cause a lot of confusion and headache. But it is more likely that the PVC is the redeployment of the original application and so you want it to be reassigned to the new PVC.&lt;br /&gt;
{{Note|text=If you delete an application, you MUST delete the PV manually when its reclaim policy is &amp;quot;Retain&amp;quot;, otherwise the data will never get deleted and could be reassigned to another claim.|type=warn}}&lt;br /&gt;
&lt;br /&gt;
===== Other volumes =====&lt;br /&gt;
Persistant volumes are probably the easiest to understand, but there are a lot of other types of volumes, most notably empty directorys and ephemoral volumes as well as volumes creating from config maps or secrets. All of these serve different purposes:&lt;br /&gt;
&lt;br /&gt;
* Empty directory - something like &amp;lt;code&amp;gt;/tmp&amp;lt;/code&amp;gt;, all data will be stored in RAM if set to writable and will be deleted when the pod restarts or is deleted. You should set a limit to how much RAM is allowed to be stored there, as it could soak up the full resources of the computer.&lt;br /&gt;
* [https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/ Emphemeral volumes] - similar to empty directorys, but instead of being stored in RAM, they are stored on disk (through persistant volumes) and will be deleted upon deletion of the pod and so mostly not really useful unless you are expecting to generate a lot of data during the running of the pod and don&#039;t want to retain them on reboot.&lt;br /&gt;
* Mounting secrets - this is read only storage and just allows you to mount configuration files that store database passwords or similar. Each key within the secret becomes a text file. Note that if its a binary file (e.g. image) you can use the &amp;lt;code&amp;gt;binaryData&amp;lt;/code&amp;gt; property and pass in a hex string.&lt;br /&gt;
* Mounting config maps - similar to secrets, but just are for files that don&#039;t have to be secret and encrypted on the machine.&lt;br /&gt;
&lt;br /&gt;
=== Cronjobs ===&lt;br /&gt;
&lt;br /&gt;
=== K3S ===&lt;br /&gt;
&lt;br /&gt;
== Deployment with Tofu ==&lt;br /&gt;
Information pertaining to terraform configuration itself can be found on [[Terraform|the Terraform page]] and for specific how to deploy to BOSS&#039; production and staging cluster, please see the [https://gitlab.bath.ac.uk/cs/int/terraform project&#039;s README] and [https://gitlab.bath.ac.uk/cs/boss/int-wiki internal wiki] for more information. This will mostly focus on the general process of deploying with tofu and the struggles.&lt;br /&gt;
&lt;br /&gt;
Please read the [[Kubernetes#K9s|K9s section]] for information about using k9s for monitoring deployments.&lt;br /&gt;
&lt;br /&gt;
Some resources may also have their own special command set to allow you to perform resource specific actions which is nice (these should be listed at the top right).&lt;br /&gt;
&lt;br /&gt;
Back to deployment, it&#039;s mostly:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu apply -var-file=./prod.tfvars [-target=module.something]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Using &amp;lt;code&amp;gt;-target&amp;lt;/code&amp;gt; can help a lot when the terraform module is huge as it will limit the number of resources it has to check for updates (though this means that you state and configuration can become desynced and so you should still do apply&#039;s without the targetting). You can even specify the exact resource you have just edited for super fast (for terraform) iterations.&lt;br /&gt;
&lt;br /&gt;
While it&#039;s deploying you cannot hit &amp;lt;code&amp;gt;Ctrl-c&amp;lt;/code&amp;gt; or well you can (twice) to force exit the application, but this can result in annoying consequencies:&lt;br /&gt;
&lt;br /&gt;
* Hitting it before you&#039;ve confirmed the plan (e.g. you accidentally forgot to include target and don&#039;t want to wait): The state &#039;&#039;&#039;WILL&#039;&#039;&#039; be locked and will not be unlocked. Therefore you have to run&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu force-unlock &amp;lt;uid&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where the &amp;lt;code&amp;gt;&amp;lt;uid&amp;gt;&amp;lt;/code&amp;gt; can be found by trying to run the apply command and it failing.&lt;br /&gt;
&lt;br /&gt;
* Hitting it while it&#039;s deploying new resources (e.g. the pods are not deploying and you didn&#039;t change the 5 minute timeout): on next run, tofu will try to recreate those resources, and so before running the command, you must open k9s, find the resources and delete them.&lt;br /&gt;
* Hitting it while updating helm charts (e.g. you didn&#039;t change the 10 minute timeout and it&#039;s not working): on the next run, tofu will refuse to deploy it, because the helm deployment is in an invalid state (&amp;quot;deploying&amp;quot;) and it will never exit this state. Therefore you must open up &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; type &amp;lt;code&amp;gt;helm &amp;lt;namespace&amp;gt;&amp;lt;/code&amp;gt;, wait a year, click &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; on the effected resource to list all the previous releases, go down to the last successful deployment and click &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; again to rollback to that release, and again wait a year while k9s refuses to respond.&lt;br /&gt;
&lt;br /&gt;
As you can probably tell deployment takes patience, expecially when you don&#039;t alter the timeouts in helm (which default to 10 minutes for creation and 10 minutes for deletion). You don&#039;t want to interrupt the flow while tofu is doing it&#039;s thing, so make sure you aren&#039;t going to have to wait 10 minutes because you made a simple typo causing the pods to crash loop.&lt;br /&gt;
&lt;br /&gt;
You can technically, temporarily edit the resources (with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; in k9s) to fix your mistake and to make it successfully deploy, so you can actually fix the mistake and redeploy (updating all the resources again) within a shorter time than it takes for tofu to timeout. You do want to be careful with timeouts however, as if you are on poor wifi (e.g. a train) or if the cluster is a bit pinned atm, deployments will take longer and the worst thing is if tofu times out but the deployment actually succeeded (though just reapply should update the state without an actualy redeploymenht unless its helm).&lt;br /&gt;
&lt;br /&gt;
=== Retaining PVs ===&lt;br /&gt;
As previously mentioned, whenever deploying new applications, you should mark any persistant volume that stores data you do not want accidentally deleted with a &amp;quot;Retain&amp;quot; reclaim policy. This can easily be done using &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; by typing &amp;lt;code&amp;gt;:pv&amp;lt;/code&amp;gt;, finding the pv attached to the claim, pressing &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; and searching for where the &amp;lt;code&amp;gt;reclaimPolicy&amp;lt;/code&amp;gt; is defined. By default it will be &amp;quot;Delete&amp;quot;, and you can just replace it with &amp;quot;Retain&amp;quot;. &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; will also show you a PV&#039;s reclaim policy on the list view which is nice to check if its already been done.&lt;br /&gt;
&lt;br /&gt;
=== Where is the state stored? ===&lt;br /&gt;
{{Note|text=State files store all passwords and secrets generated or collected by terraform unencrypted by default and so you should be extremely cautious with where the files are stored, who has access to them and never commit them.|type=warn}}&lt;br /&gt;
Usually, if you don&#039;t define any backends, it is stored in a &amp;lt;code&amp;gt;.tfstate&amp;lt;/code&amp;gt; file within the directory of your folder. If you are just managing your cluster with them, an easy place to store it is within the kubernetes cluster itself, using the kubernetes backend:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
terraform {&lt;br /&gt;
  # ...&lt;br /&gt;
&lt;br /&gt;
  backend &amp;quot;kubernetes&amp;quot; {&lt;br /&gt;
    secret_suffix  = &amp;quot;my-cluster&amp;quot;&lt;br /&gt;
    config_paths   = [var.kube_config_path]&lt;br /&gt;
    config_context = var.kube_context&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  # ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This is BOSS&#039;s method of storage as we do not have to trust the GitLab instance with our cluster&#039;s life. But others can be chosen, for example [https://docs.gitlab.com/user/infrastructure/iac/terraform_state/ within GitLab itself], although this should be done with extreme caution due to the risk of exposing plan files (which contain secrets) to the world.&lt;br /&gt;
&lt;br /&gt;
=== Managing the state ===&lt;br /&gt;
As we can never write perfect code the first time round, some MRs will result in refactorisation of components into new modules. Therefore state management is important. This annoyingly is painful especially with larger effected packages and so I would recommend considering the effects of destroying all the effected resources and reapplying them (which is what  &amp;lt;code&amp;gt;tofu apply ...&amp;lt;/code&amp;gt; will do by default). If you cannot afford this (e.g. with persistant volumes) you will have to use &amp;lt;code&amp;gt;tofu state mv&amp;lt;/code&amp;gt; which works by moving the specified address to another (make sure there is no typos though!&lt;br /&gt;
&lt;br /&gt;
However this requires the resource type to be the same through the move, which sometimes may not happen. At which point you&#039;ll want to delete the state information and re-import it. E.g.&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu state rm module.example.my_resource.name&lt;br /&gt;
# Import the module (Note: the format will differ depending on the provider)&lt;br /&gt;
tofu import module.example.module.refractorisation.my_new_resource.name my_resource/name&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Updating packages ===&lt;br /&gt;
To make upgrading easier, in submodules, packages are pinned to the nearest major version. You can then update the base package version and run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu init -upgrade&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Which will update the package versions on your machine and update the lock file.&lt;br /&gt;
&lt;br /&gt;
If you do not specify &amp;lt;code&amp;gt;-upgrade&amp;lt;/code&amp;gt; it will just update the module list (e.g. if you add a new application with a new use of &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== K9s ==&lt;br /&gt;
[https://k9scli.io/ k9s] is recommended for watching deployments and seeing why they failed as its generally just fantastic the more you get used to it. The common shortcuts you need to know are:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; will describe the currently selected object. If you go to the bottom (&amp;lt;code&amp;gt;Shift-G&amp;lt;/code&amp;gt;) you will be able to see events associated with that resource, for pods and statefulsets this is &#039;&#039;&#039;extremely useful&#039;&#039;&#039; as it includes failures for pulling images or security denials.&lt;br /&gt;
* &amp;lt;code&amp;gt;l&amp;lt;/code&amp;gt; see the logs of all containers associated with the resource.&lt;br /&gt;
* &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; while within a secret/configmap will show you the raw (string) data&lt;br /&gt;
* &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; to shell into a pod (note that some pods do not support this as they do not ship withsh&lt;br /&gt;
* &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; allows you to perform a rolling restart on a statefulset or deployment (meaning there should be no downtime if everything is configured). This is the recommended way to update a pod on production (&#039;&#039;&#039;do not&#039;&#039;&#039; just delete the pod itself).&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;enter&amp;gt;&amp;lt;/code&amp;gt; see any subresources (e.g. containers for pods, or pods for deployments/statefulsets)&lt;br /&gt;
* &amp;lt;code&amp;gt;Ctrl-d&amp;lt;/code&amp;gt; allows you to delete the resource&lt;br /&gt;
* &amp;lt;code&amp;gt;Ctrl-f&amp;lt;/code&amp;gt; allows you to add a port-forward to your own machine (really useful for debugging if an application is responding but is not available via traefik), or getting direct access to databases.&lt;br /&gt;
* &amp;lt;code&amp;gt;:&amp;lt;resource&amp;gt; &amp;lt;namespace|all&amp;gt;&amp;lt;/code&amp;gt; will change the list view to the given resource in the namespace&lt;br /&gt;
* &amp;lt;code&amp;gt;:&amp;lt;resource&amp;gt;&amp;lt;/code&amp;gt; will change the list view for the given resource in the currently selected namespace&lt;br /&gt;
* &amp;lt;code&amp;gt;0-9&amp;lt;/code&amp;gt; are shortcuts to switch between recently selected namespaces (they should show at the top with the current assignment)&lt;br /&gt;
&lt;br /&gt;
== Migrating storages ==&lt;br /&gt;
When you are migrating clusters to a new machine (just copy the VM though) or just moving statefulsets between namespaces (persistant volumes are namespace agnostic though), you may need to migrate persistant volumes. As I have done this multiple times now, and so here is my steps to do so.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=All pods using these volumes should be inaccessible during the process of this. This can be done by temporarily deleting any &amp;lt;code&amp;gt;HTTPRoute&amp;lt;/code&amp;gt;s to the service.}}&lt;br /&gt;
&lt;br /&gt;
=== Is it a database? ===&lt;br /&gt;
Databases have users and passwords associated, which can cause issues if you are redeploying a whole cluster with terraform as new passwords will be generated. You can import new values but that&#039;s boring. Instead you can just us &amp;lt;code&amp;gt;pg_dump&amp;lt;/code&amp;gt; (or equivalent):&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl port-forward -n my_namespace service/my_pod 5432:5432 &amp;amp;&lt;br /&gt;
pg_dump $DATABASE_URL &amp;gt; my_pod_bkp.sql&lt;br /&gt;
killall kubectl # Stop port-forward&lt;br /&gt;
# ...&lt;br /&gt;
# Apply the SQL to the volume in the new cluster:&lt;br /&gt;
kubectl --context new_cluster port-forward -n my_namespace service/my_port 5432:5432 &amp;amp;&lt;br /&gt;
psql -U username -d myDataBase -a -f my_pod_bkp.sql&lt;br /&gt;
killall kubectl # Stop port-forward&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Anything else ===&lt;br /&gt;
You will need to investigate permissions and such, but kubectl has this [https://kubernetes.io/docs/reference/kubectl/generated/kubectl_cp/ &amp;lt;code&amp;gt;cp&amp;lt;/code&amp;gt; command] which you can use to download whole directories. But before that you should run &amp;lt;code&amp;gt;ls -al&amp;lt;/code&amp;gt; while ssh-ed into the machine using the volume to view the permissions of the files.&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl cp my_namespace/pod:/where/is/pv/mounted bkp_dir&lt;br /&gt;
# ...&lt;br /&gt;
kubectl --context new_cluster cp bkp_dir my_namespace/pod:/where/is/pv/mounted&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;If the pod cannot run without the storage being set. You have two options: set it with basic data to be overridden, or create a dummy pod with the PV mounted and copy it to there.&lt;br /&gt;
&lt;br /&gt;
Remember to mark any new PVs that should retain data [[Kubernetes#Retaining PVs|with correct reclaim policy]].&lt;br /&gt;
&lt;br /&gt;
== Multinode clusters ==&lt;br /&gt;
[[Bath Open Source Society|BOSS]] has specifically chosen not to use multinode clusters and so this is just for the interested people, and hopefully explain why we did not go down this route. Yes, multinode clusters are the whole point of kubernetes, where the program chooses the at least somewhat optimal location to deploy an application and automatically move it to another node if the one its on crashes or restarts for an update. But there are also benefits of kubernetes outside of this feature, such as proper permission system as well as providing a somewhat clean interface for managing deployments.&lt;br /&gt;
&lt;br /&gt;
Additionally, we felt that as a lot of companies are moving towards managing deployments, at least partially, with kubernetes, our use of it will hopefully provide easy place for students to experiment and learn about it (as this is stuff university will never teach you). It should be noted that if companies actually cared about getting the most &amp;quot;bang for your buck&amp;quot;, it is worth more to manage deployments yourself and choose which nodes host them, though this can produce more overhead.&lt;br /&gt;
&lt;br /&gt;
=== Benefits ===&lt;br /&gt;
There are clear benefits of running a multinode cluster, node down? That&#039;s fine, all applications can still run just fine and are accessible. Updating your entrypoint? That&#039;s fine, just point your assumingly IPv4 to another node, or if you&#039;re using IPv6 just setup DNS to point to both nodes. With my multinode cluster, I am writing this currently after one node completely bricked itself and the other node down after I completely bricked it and all applications are still running fine on the three remaining nodes.&lt;br /&gt;
&lt;br /&gt;
=== Downsides ===&lt;br /&gt;
With all this new power comes significant overhead though. For one, if a node goes down its not always as simple as kubernetes automatically moving things. Firstly, volumes may only be stored on that one node, then they may only be locked to that one node with &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;. So if the machine didn&#039;t shutdown properly (or if one just get&#039;s disconnected from the control plane) you will have to make intervention steps as soon as possible, though, you don&#039;t have to be in a panic to get the node itself back up. If you have correctly [[Kubernetes#Safely shutting down nodes|setup safe shutdowns]] and its a routine update though, you probably don&#039;t need to touch it.&lt;br /&gt;
&lt;br /&gt;
Power, is another one, the more nodes, the more your [[Kubernetes#Control plane|control plane]] has to manage, meaning the more its going to be stuck managing the cluster, and so will not be able to run their own pods. This means that going from a single node cluster to a multinode cluster, you will probably need at least 3-4 additional machines for it to be managed and working properly. Yes you could run a 2 or 3 node cluster, but the benefits really are not there for the massive amount of downtime. On top of this, you probably want at least 1 node of wiggleroom capable of handling your highest power node, this means that you can never reach the full potential of your cluster, as both your control plane and your other nodes must be able to take the strain when that node is disconnected.&lt;br /&gt;
&lt;br /&gt;
Storage is probably the largest issue that you will need to tackle, and is the one that put BOSS of going this route (along with the fact that we wanted to keep some of our servers as hot spares if parts broke). This will be explored more in the [[Kubernetes#How to manage Storage|&amp;quot;How to manage Storage&amp;quot; section]], but the summary is, although there are tools out there, they require a minimum node count of 3 and have significant overhead. Additionally, because of the potential constant movement of data between nodes going down (even for general updates), it causes additional wear and tear on your drives, reducing their life expectancy (I killed a brand-new SSD in 6 months seemingly because my nodes were going down for updates so often). &lt;br /&gt;
&lt;br /&gt;
Networking is a minor one, all nodes need to be connected to control plane and cannot get randomly interrupted or cut off (e.g. you have your control plane in one room hooked up to a UPS and a agent node in another on a UPS, but the router that connects them is not on the UPS). Disconnection at critical times can cause the control node to start moving workloads off the disconnected node onto others, which with statefulsets or &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt; volumes can cause desynchronisation which is a massive issue and will cause corruption. Finally there is just a minor issue that if you don&#039;t setup correctly you will probably be quite confused, Traefik forwarding going between nodes may result in the final application being given the wrong IP address.&lt;br /&gt;
&lt;br /&gt;
Finally, there is also a bit of a weird one, managing SELinux policies and kernel modules across the cluster. I have had multiple issues in the past of minor configuration differences between my nodes resulting in a pod crashlooping on one node and absolutely just fine on another. Therefore this creates additional overhead as you then have to make sure all installs are identical and then potentially install the security profile operator (which is great when it works but not so great when the docker container they use is ancient and doesn&#039;t work on the latest systems and k3s accidentally breaks support for it). &lt;br /&gt;
&lt;br /&gt;
And so, in summary, multinode clusters are amazing for chaising that 99.99999999999% uptime and really interesting for learning the difficulties with it and all the solutions out there. But it is really not for the faint of heart, I would recommend trying with a bunch of VMs on a single node just to understand how it works, but if you are just a hobbiest wanting to host some basic applications at home, do not use it. I am planning on deconstructing my cluster and replacing it with a single node similar to [[Bath Open Source Society|BOSS]] after I leave univesity.&lt;br /&gt;
&lt;br /&gt;
=== Control plane ===&lt;br /&gt;
The first challenge is the control plane. What&#039;s the benefit of a multinode cluster when it relys on a single node for sending control signals. If it goes down, the whole cluster will panic if a pod crashes, it is left in a seriously vulnerable position. Therefore, you instead need to expand your control plane to multiple nodes. K3s offers built in solution for this, [https://docs.k3s.io/datastore/ha-embedded?_highlight=etcd utilising &amp;lt;code&amp;gt;etcd&amp;lt;/code&amp;gt;] by default to share the cluster state between control nodes, or you can just move it off to [https://docs.k3s.io/datastore/ha a separate database cluster] (which is what&#039;s recommended in production as you probably have another spare 3 machines). Then you must setup a load balancer between the control nodes (which actually helps with not overloading a single control node). But now the issue is that you are relying on a single load balancer. As a hacky solution, I setup a load balancer on all my control nodes which balanced the load between itself and the other nodes. This mean that the DNS record could store all IPs associated with the nodes and hopefully mitigate any communication issues when a single one went down.&lt;br /&gt;
&lt;br /&gt;
However, when setting up etcd, you must remember to point the &amp;lt;code&amp;gt;--server&amp;lt;/code&amp;gt; to the load balancer and not a single node (as then the single node becomes the single point of failure).&lt;br /&gt;
&lt;br /&gt;
=== How to manage Storage ===&lt;br /&gt;
I personally used [https://longhorn.io/ longhorn], which provided a somewhat easy solution, you just have to change the default storage class and migrate all your PVs. It requires that you have 3 nodes to share data between (as it can then use voting logic to ward off corruption). By default, it then stores volumes on three different nodes at the same time, meaning if one node goes down, it will just make a copy to a new node with the remaining two copies. Additionally, if a pod is assigned to a node without a copy of the data, longhorn can simply make a new copy onto that node (however it is more likely that kubernetes will choose a node that already has the storage).&lt;br /&gt;
&lt;br /&gt;
It then provides a UI where you can easily manually update all the storage volumes after an update of longhorn and also manage automated backups and snapshots. It is actually a really handy tool and generally really cool (though you have to manually label PVs with the types of backups you want).&lt;br /&gt;
&lt;br /&gt;
The issue? It uses around 2GiB of RAM on each individual node. Then if your nodes are constantly going down (e.g. automated updates every week), it will instantly start panicking and moving volumes over which can wear out drives. Additionally, if too many nodes go down, it can be the single source of crashing everything else because of the amount of resources it can use.&lt;br /&gt;
&lt;br /&gt;
=== Safely shutting down nodes ===&lt;br /&gt;
This is basically a requirement if you actually want to obtain the full benefits of multinode cluster that requires as little manual intervention as possible (which is still somehow more intervention than a single node cluster IMO). There is a great [https://oranki.net/posts/2025-01-09-graceful-k3s-shutdown/ blog on &amp;lt;code&amp;gt;oranki.net&amp;lt;/code&amp;gt;] on how to set this up, but effectively you need to set that anytime the &amp;lt;code&amp;gt;k3s&amp;lt;/code&amp;gt; service is stopped you run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl drain --ignore-daemonsets --delete-emptydir-data &amp;lt;node&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Then when it comes back up you need to run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl uncordon &amp;lt;node&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;As explained in the blog post, you can do this via a systemctl service.&lt;br /&gt;
&lt;br /&gt;
What this does is makes sure all nodes are safety migrated onto another node before a shutdown can take place, effectively tainting the node (meaning can be assigned to it). Then you must &amp;lt;code&amp;gt;uncordon&amp;lt;/code&amp;gt; a node (meaning removing the taint) to tell the control plane that you can now place pods on it again.&lt;br /&gt;
&lt;br /&gt;
== Backups ==&lt;br /&gt;
Within kubernetes, I&#039;d say there are 3 types of backups:&lt;br /&gt;
&lt;br /&gt;
* Whole OS/VM backups (really easy if host is running ZFS, but large)&lt;br /&gt;
* Resource configuration backup (saves time but if you are using terraform, not particularly useful): [https://velero.io/ velero]&lt;br /&gt;
* Persistant volume backup (small and application specific): built into some storage classes (e.g. [https://longhorn.io longhorn]) or you can use [https://k8up.io/ k8up]&lt;br /&gt;
&lt;br /&gt;
All of which you may want to use, however because I velero isn&#039;t amazing (It fails to back up PVs and the helm chart depends on paid pods, I prefer mixing a whole VM backup with ZFS snapshots and replication, and then do per volume backups with k8up for really important data to offsite cloud S3 bucket.&lt;br /&gt;
&lt;br /&gt;
== Alerting ==&lt;br /&gt;
Alerting is really important, for this I would simply just point to the [https://prometheus-operator.dev/ prometheus operator] or the [https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack kube-prometheus-stack], as these provide really easy methods to automatically monitor the clusters and send emails when something might be going wrong (but it can be extremely noisy) and node exporter requires additional configuration for SELinux enabled systems. This provides a nice standardised system that a lot of helm charts implement (and offer grafana boards you can import).&lt;br /&gt;
&lt;br /&gt;
When you get an email, you should investigate what the error actual means and what are the recommended steps to fix. E.g. if a job fails, you can either delete it or rerun it until it works again. For CPUThrottlingHigh errors, I still have absolutely no clue what you are supposed to do about it other than increasing the permitted resources for a pod (which isn&#039;t recommended).&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Kubernetes event log ===&lt;br /&gt;
&lt;br /&gt;
=== Traefik dashboard ===&lt;br /&gt;
&lt;br /&gt;
=== Shell-ing into pods ===&lt;br /&gt;
&lt;br /&gt;
=== K3S Log ===&lt;br /&gt;
&lt;br /&gt;
=== Emergency Debug pods ===&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare API tokens ===&lt;br /&gt;
&lt;br /&gt;
=== SELinux ===&lt;br /&gt;
&lt;br /&gt;
=== Tofu ===&lt;br /&gt;
&lt;br /&gt;
=== Scenarios ===&lt;br /&gt;
&lt;br /&gt;
==== Deployment/statefulset created but pod not creating ====&lt;br /&gt;
&lt;br /&gt;
==== Pod in cash loop ====&lt;br /&gt;
&lt;br /&gt;
==== Pod cannot request any website ====&lt;br /&gt;
&lt;br /&gt;
==== Pod can access the internet but everything returns self-signed certificate ====&lt;br /&gt;
&lt;br /&gt;
==== Deployment can&#039;t access its database/valkey ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is requesting denied system privileges ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 404 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 403 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Authelia refuses to start up ====&lt;br /&gt;
&lt;br /&gt;
==== Node is crashing often after running out of RAM ====&lt;br /&gt;
&lt;br /&gt;
==== On IPV6 cluster and requests randomly timeout or return 404 ====&lt;br /&gt;
&lt;br /&gt;
==== Statefulset refusing to start pod (PVC) ====&lt;br /&gt;
&lt;br /&gt;
==== A node just crashed and went offline ====&lt;br /&gt;
&lt;br /&gt;
=== Cleaning up ===&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=146</id>
		<title>Kubernetes</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=146"/>
		<updated>2026-06-07T12:11:55Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Add multinode cluster info and some stuff on migrating storage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I ([[User:Hw2210|hw2210]]) have been asked to write down my processes for working with kubernetes and terraform as I am leaving this year, and hopefully it will serve as useful information to any future sysadmin. This page is the output of this and is written to be as generic as possible, so if you are just experimenting with kubernetes on a home lab, please feel free to read and hopefully you will learn somethings. There will be multiple references to [[BOSS/Hosting/Cluster|BOSS&#039;s cluster]], which has all the security boxes ticked on and so we have to deal with security contexts, network policies and [[SELinux]] as they are the bain of all problems.&lt;br /&gt;
&lt;br /&gt;
== General knowledge ==&lt;br /&gt;
This tries to cover some basic concepts, focusing on common confusion, but it will skip over a lot of the general knowledge information such as secrets and configmaps. The kubernete&#039;s documentation is pretty good, though difficult to read at some points, but there are loads of great tutorials explaining how kubernetes works.&lt;br /&gt;
&lt;br /&gt;
=== Pod vs Container ===&lt;br /&gt;
A common confusion is that [https://kubernetes.io/docs/concepts/workloads/pods/ pod]&#039;s are containers in kubernetes. This is not exactly true, a pod is a general group of linux namespaces which can host multiple containers. This means you can have a container that writes to a directory and another container that reads from that directory in the same pod. This can be very powerful, but in a lot of cases can be ignored.&lt;br /&gt;
&lt;br /&gt;
But it is key to point out that a Pod is a resource that is created by other kubernetes resources. They are a group of processes running, once they die the pod is deleted and forgotten about. Therefore you should not be creating pods directly, instead you should be using deployments, statefulsets, cronjobs or even jobs. All these resources create generate a pod as their lifecycle and will restart/recreate the pod if it fails.&lt;br /&gt;
&lt;br /&gt;
=== Statefulset vs deployment ===&lt;br /&gt;
Another key understanding is the difference between statefulsets and deployments, as statefulsets can cause some confusion in how they work. The difference is more applicable to multinode clusters but are still key to the structure of kubernetes.&lt;br /&gt;
&lt;br /&gt;
Effectively, a statefulset is a deployment with writable volumes - known as persistent volumes (PV). Having the ability to write to volumes can cause race conditions when multiple pods across nodes are writing to the same file. This is where statefulsets come in, they lock volumes and so they can only be used by one node and one pod, with scaling creating new persistant volumes which are stored separately. This means that if you scale a statefulset that relies on shared knowledge in the volume, half your requests will have one set of data and the other half will have another.&lt;br /&gt;
&lt;br /&gt;
This obviously is quite a big disadvantage and can lead to confusing behaviour when a node is not configured to shutdown safely and taint itself, moving all the statefulsets off of itself before it shutsdown - if PV is locked by a node and pod, it cannot be deployed to another cluster.&lt;br /&gt;
&lt;br /&gt;
Therefore, this is where deployments come in, they, usually, do not have associated persistent volumes, allowing for easy horizontal scaling. For storing shared data, they should connect to a database on another node which can be more compatible with statefulsets when configured correctly.&lt;br /&gt;
&lt;br /&gt;
Both of these resources will create pods and redeploy them if they crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=If you update any &amp;quot;volume&amp;quot; attribute within a statefulset ALL volumes will be deleted and recreated. To not lose any data, please make sure that the PV&#039;s reclaim policy has been set to &amp;quot;Retain&amp;quot;. You should generally do this for any data you do not want to lose.|type=warn}}&lt;br /&gt;
&lt;br /&gt;
==== Liveness/Startup probes ====&lt;br /&gt;
Liveness and startup probes can be defined on pods, and these let kubernetes know if a pod has started correctly and if it still is alive. For example, some deployments might take a while to start up and configure everything before it starts serving content and so when restarting, this can cause some downtime. Downtime is what we are trying to avoid and so by using a startup probe, kubernetes knows that this application is ready, and so it will only terminate the previous node once the new one is started up resulting in zero downtime!&lt;br /&gt;
&lt;br /&gt;
The liveness probe on the other hand periodically checks whether the pod is still alive. This means that if it suddenly stops responding due to a long database query, kubernetes can detect that and replace the pod with another further reducing downtime. However, this usually suggests something else is wrong with the application and so this should be investigated and fixed.&lt;br /&gt;
&lt;br /&gt;
==== Security Context ====&lt;br /&gt;
{{Note|text=Within [[BOSS/Hosting/Cluster|BOSS&#039;s kubernetes cluster]], we define a security policy which requires all pods to correctly define their security context and make sure that it is not running as root.}}&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ security context] defines what privileges the pod has when running, we effectively want this to be as minimal as possible to reduce attack surface area. E.g.&lt;br /&gt;
&lt;br /&gt;
* Run as user&lt;br /&gt;
* Don&#039;t allow privilege escalation&lt;br /&gt;
* Properly define seccomp policy&lt;br /&gt;
* Default SELinux container context&lt;br /&gt;
* Drop all capabilities&lt;br /&gt;
&lt;br /&gt;
However this can cause issues with third-party applications which commonly do some questionable things, e.g. require running as root or changing the uid. But for our pods you can mostly just copy and paste:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  # ...&lt;br /&gt;
  spec {&lt;br /&gt;
    # ...&lt;br /&gt;
    template {&lt;br /&gt;
      # ...&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          # ...&lt;br /&gt;
&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          # ...&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[Terraform]] for more information.&lt;br /&gt;
&lt;br /&gt;
=== What is a CRD? ===&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ Custom Resource Definition (CRD)], allows you to extend kubernetes capabilities and define custome resources. This is usually paired with an operator which reads the resources and performs some actions.&lt;br /&gt;
&lt;br /&gt;
We should never create our own, but third-party ones make it much easier for doing things such as creating ingress routes with traefik or define database clusters with our postgres operator.&lt;br /&gt;
&lt;br /&gt;
K9s and kubectl support these out of the box (as they are basically just schemas for yaml configuration), and you can see all pods by using the name of the resource.&lt;br /&gt;
&lt;br /&gt;
=== Traefik and gateways ===&lt;br /&gt;
[[File:Gateway diagram.svg|thumb|368x368px|A digram depicting the the flow of traffic from the internet, to traefik then to each namespace&#039;s gateway. Each gateway then looks at all connected certificates to add TLS authentication and then looks at the HTTPRoute to find which one matches based on the rules which then defines what service to forward the traffic to and subsequently the pods.]]&lt;br /&gt;
Kubernetes works by defining services, which give a common endpoint to call potentially multiple pods. These can then be exposed through HTTPRoutes and the [https://kubernetes.io/docs/concepts/services-networking/gateway/ Gateway API], in which traefik implements.&lt;br /&gt;
&lt;br /&gt;
The Gateway API resources are read by [https://doc.traefik.io/traefik/ traefik], which acts as the implementation, and acts accordingly to the defined configuration. Therefore, in essence, the gateways act only as a means for configuring traefik. But effectively, traefik has configured open ports it can expose, it then looks for Gateways, in the permitted namespaces, for their configuration. The gateways stores a list of ports that the namespace can expose (though it cannot add one that is not included within traefik configuration itself), as well as a list of certificates. At this point, the domain requested must have a certificate configured within the gateway, and all TLS logic is handled by traefik and so all further traefik is effectively decrypted. Notice here that if a certificate is not configured on the gateway, it cannot be served (one of the downsides of the gateway API).&lt;br /&gt;
&lt;br /&gt;
For us, we have decided to have each namespace have their own gateway, due to the protections traefik offers, this means that we do not have to do any cross namespace references for certificates, and do not have to update the main gateway anytime we need to add a certificate. There is an additional issue with this, is that during the time the certificate doesn&#039;t exist but is configured (e.g. when first request it), the gateway is deemed invalid and so doesn&#039;t route any traefik (even http). There is a plan to help mitigate this through the use of &amp;lt;code&amp;gt;ListenerSets&amp;lt;/code&amp;gt; but this is yet to be supported in traefik and still has this issue. Therefore, we want to make sure that a single gateway hosts services for as few applications as possible (preferably only one).&lt;br /&gt;
&lt;br /&gt;
Anyway, the gateway will have a number of child routes (&amp;lt;code&amp;gt;TLSRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HTTPRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GRPCRoutes&amp;lt;/code&amp;gt; and coming in the future &amp;lt;code&amp;gt;TCPRoute&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;UDPRoute&amp;lt;/code&amp;gt;). These routes act as queries to determine when and what traefik should forward to. So for example they act as:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
if hostname is example.bathcs.com forward to example-service&lt;br /&gt;
if the path starts with /api forward to api-service&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Then traefik can request those services, allowing kubernetes to effectively take over, looking at the pods associated with the service and using the defined algorithm to send the request to those pods and using the defined ports.&lt;br /&gt;
&lt;br /&gt;
This does mean there are a number of different places a port can change:&lt;br /&gt;
 Exposed port -&amp;gt; Traefik internal port -&amp;gt; Service port -&amp;gt; Pod port -&amp;gt; Application port&lt;br /&gt;
In most cases you should have the service port, pod port and application port all matching, this makes debugging a lot easier. Additionally there are few reasons why you will want to change traefik&#039;s internal port and the exposed port (but there are some!).&lt;br /&gt;
&lt;br /&gt;
==== Certificates ====&lt;br /&gt;
The thing with certificates is that we effectively never want to manually create them, the recommended expiry time for certificates is always dropping, with the most recent update at 45 days. This is way too much work for manual requesting and uploading and adds too many layers for it to go wrong. Therefore we use cert manager, which allows defining certificate objects within the cluster, cert manager will then go and do all the requesting for us and store it in a secret. Then it will also track the expiry and automatically update the certificate a week or so before it expires.&lt;br /&gt;
&lt;br /&gt;
There are multiple different methods it can use to validate that we are in fact in charge of the domain:&lt;br /&gt;
&lt;br /&gt;
* DNS - this is the preferred method, as it allows us generating certificates for protected IPs. But this requires a valid cloudflare API token (which is restricted to a single IP).&lt;br /&gt;
* HTTP - this is when the certificate authority will request our server from multiple locations, which means the DNS cannot be set to a protected IP. But it means we can generate certificates for domains that we don&#039;t control the DNS of (e.g. bath.ac.uk hostnames) but it is at least configured to point to our server. The integration with traefik means that there is no additional work required for the application to get these working.&lt;br /&gt;
* Cloudflare Origin - These are very special certificates and cannot be decrypted by the browser. The idea is that by generating these certificates, only cloudflare themselves will be able to decrypt the contents and so only they can proxy your IP. We use this specifically for [[Kubernetes#Cloudflare proxy|Cloudflare proxy]]-ing thought it doesn&#039;t provide us the true benefits (given our IP is still public)&lt;br /&gt;
&lt;br /&gt;
Within cert manager&#039;s speak, these are known as issuers, and we have cluster issuers defined for each (meaning any namespace in the cluster can use them).&lt;br /&gt;
{{Note|text=When cert manager is first requesting the certificate, the configured gateway will be invalid and so no routes attached will forward traffic.|type=reminder}}&lt;br /&gt;
&lt;br /&gt;
==== Cloudflare proxy ====&lt;br /&gt;
Cloudflare proxy offers the benefits of caching our content on &amp;quot;edge&amp;quot; servers, meaning that our websites perform much better on average as well as it can protect the IP of the machine, but as explained later we don&#039;t use cloudflare proxy everywhere and so lose this advantage. This caching is amazing when the application is configured for it to work well with it (e.g. correctly labelling requests as cachable). But it does not work with every application, especially third-party services which sometimes just break when using it. But it also adds troubling security questions, for example, a login page will also be proxied, and decrypted by cloudflare, resulting in cloudflare having access to all passwords that go through the site. For this reason we limit where we use cloudflare proxying to services that would benefit heavily from it (e.g. this Wiki as authentication is handled offsite).&lt;br /&gt;
&lt;br /&gt;
To setup cloudflare proxying, it is as simple as generating a certificate with the cloudflare origin issuer and exposing a HTTPRoute with the certificate and then enabling proxy in the dns record. Obviously this does not work with internal DNS records (e.g. &amp;lt;code&amp;gt;k8s.bathcs.com&amp;lt;/code&amp;gt;) and so our terraform config automatically detects and does not proxy this stuff.&lt;br /&gt;
&lt;br /&gt;
The cloudflare origin issuer then speaks to the cloudflare origin operator which requests a certificate from cloudflare themselves. The generated certificates can be found in the cloudflare dashboard for the domain under &amp;quot;SSL/TLS &amp;gt; Origin Server&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Network Policies ===&lt;br /&gt;
With additional security, comes additional policy managment. [https://kubernetes.io/docs/concepts/services-networking/network-policies/ Network policies] tell kubernetes where a pod is allows to send and receive traffic. However they are a bit confusing at times, and so can cause some headache when trying to debug why your pod cannot communicate with your database.&lt;br /&gt;
&lt;br /&gt;
* By default ALL outgoing traffic is allows.&lt;br /&gt;
* By default NO incoming traffic is allows (except from traefik).&lt;br /&gt;
&lt;br /&gt;
So by default, any pod is allows to communicate with any port on the internet, but not allows to communicate with any other pod in the whole cluster. In the futher we hope to disallow both by default, and so you will have to specify exactly what ports (and potentially where) your pod should be communicating.&lt;br /&gt;
&lt;br /&gt;
Within the terraform, we have helper functions built into the utilities for the databases to automatically generate network policies for incoming traffic, relying on the requirement of adding a label to your pod, they are usually outputted by the module under &amp;lt;code&amp;gt;client_labels&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
To define your own policy (in terraform ofc), there are two parts &amp;lt;code&amp;gt;ingress&amp;lt;/code&amp;gt; (incoming traffic) and &amp;lt;code&amp;gt;egress&amp;lt;/code&amp;gt; (outgoing traffic). Both of these can then be a list of rules matching pods that will be allows. They are additive, meaning that all network policies matching the pod will be combined to produce the final ruleset.&lt;br /&gt;
&lt;br /&gt;
The main rules you will want to focus on are:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;ip_block&amp;lt;/code&amp;gt; defines a lock of IPs where the traffic originates or is going to&lt;br /&gt;
* &amp;lt;code&amp;gt;namespaceSelector&amp;lt;/code&amp;gt; defines the labels matching on the namespaces where traffic is allows to/from&lt;br /&gt;
* &amp;lt;code&amp;gt;podSelector&amp;lt;/code&amp;gt; same as namespaces but specific to pods themselves (e.g. what our databases do)&lt;br /&gt;
* &amp;lt;code&amp;gt;ports&amp;lt;/code&amp;gt; defines the ports allows by these connections&lt;br /&gt;
&lt;br /&gt;
While defining any egress policies you must remember to include basic services, e.g. DNS and maybe NTP. An example full configuration might look like:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_network_policy_v1&amp;quot; &amp;quot;example&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;my-policy&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    egress {&lt;br /&gt;
      # Allow pod to use DNS&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 53&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 53&lt;br /&gt;
        protocol = &amp;quot;UDP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      # Allow pod to request its database&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 5432&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    ingress {&lt;br /&gt;
      # Allow pods within namespaces that have enabled ldap (with the defined&lt;br /&gt;
      # label) to request this pod via the &amp;quot;ldap&amp;quot; port&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = &amp;quot;ldap&amp;quot;&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      from {&lt;br /&gt;
        namespace_selector {&lt;br /&gt;
          match_labels = {&lt;br /&gt;
            allow_ldap = true&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    # This defines the pod that this network policy will be applied to.&lt;br /&gt;
    pod_selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = &amp;quot;affected-pod&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    # We have defined both Ingress and Egress rules for this pod&lt;br /&gt;
    policy_types = [&amp;quot;Ingress&amp;quot;, &amp;quot;Egress&amp;quot;]&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This shows an example configuration, allows the defined pod to communicate with a database (note that the database will also be required to have a network policy with an ingress rule allowing the pod to connect) and DNS and allow other pods in the cluster to communciate with it. Note that any http port is missing as traffic is automatically permitted to access any port within the cluster.&lt;br /&gt;
&lt;br /&gt;
=== Helm ===&lt;br /&gt;
[https://helm.sh/ Helm] is a tool which allows the deployment of a set of kubernetes resources from a single configuration. So for third party, complicated applications its amazing, and there are a lot hosted on [https://artifacthub.io/ artifacthub]  and other random places (as you can really easily host a helm repo for free). There are benefits, like being able to rollback a change to a previous version. However, there are a few things to note:&lt;br /&gt;
&lt;br /&gt;
* When configuring in terraform, do NOT add repos! The full url should go in the &amp;lt;code&amp;gt;repo&amp;lt;/code&amp;gt; config, or if its an oci url, it should go in the &amp;lt;code&amp;gt;chart&amp;lt;/code&amp;gt; attribute.&lt;br /&gt;
* Tofu will refuse to deploy a deploying helm chart (you have to rollback first)&lt;br /&gt;
* &#039;&#039;&#039;DO NOT STORE SECRETS IN VALUES&#039;&#039;&#039; - values are not stored securely, and so you should never store passwords or api keys directly in the values (this is an easy mistake to make when configuring), all previous sets of values will be stored forever in the cluster. Therefore, if you make this mistake, you will have to rotate the secret or delete the whole helm deployment and start again.&lt;br /&gt;
* Helm does not care if resources are changed between deployments - this is both good and bad, it means that you can apply &amp;quot;hacks&amp;quot; to helm charts you know will not change and they will not appear in the terraform plans to be fixed. But again this is not particularly good practice and can result in some confusing behaviour.&lt;br /&gt;
* When you delete a deployment, all persistant volumes will get wiped unless they have the &amp;quot;Retain&amp;quot; reclaim policy.&lt;br /&gt;
&lt;br /&gt;
Helm is a great tool for quickly deploying whole clusters of applications, but it should be used with caution, making sure the chart is reputable and well maintained. As we are using terraform for deployments, it should also only be used for third party applications.&lt;br /&gt;
&lt;br /&gt;
=== Persistant Volumes ===&lt;br /&gt;
[https://kubernetes.io/docs/concepts/storage/persistent-volumes/ Persistant volumes] (PVs) and persistant volume claims (PVCs) are one of the more convoluted things in kubernetes and one of the more dangerous as you are handling data.&lt;br /&gt;
&lt;br /&gt;
At a high level, you request storage by creating a persistant volume claim, the storage manager fullfills your claim by creating a persistant volume (which does not have an associated namespace) and then it is assigned to your pod. You should never be creating persistant volumes yourself, and probably want to be creating persistant volumes through the statefulset&#039;s template interface.&lt;br /&gt;
&lt;br /&gt;
For us, the storage operator is k3s&#039; built in one, but for clusters with multiple pods, it probably is going to be something like [https://longhorn.io/ longhorn], which manages keeping replicas of the storage on multiple machines. This brings up a core issue, a persistant volume can only be mounted to a pod on a node that stores the persistant volume, which is why longhorn is necessary on multi-node clusters and is why we decided to only have one node in each of our clusters.&lt;br /&gt;
&lt;br /&gt;
PVs have different types of [https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes access modes]: &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ReadOnlyMany&amp;lt;/code&amp;gt; which mostly control how many nodes can read or write to it at once (multiple pods can still mount it, as long as they are all on the same node). So for k3s&#039; storage class does not support &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ReadOnlyMany&amp;lt;/code&amp;gt;, so you should only be setting it to &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Reclaiming PVs is one of the big danger factors. By default a persistant volume will be deleted if there are no longer any claims for it, and the claims will probably get deleted (e.g. in helm and statefulsets). Therefore, if you have important data on a pod, you most likely want to set the [https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaim-policy reclaim policy], that means the volume cannot be automatically deleted, but it also means, if another PVC comes along and matches the PV, the PV will be assigned to the new claim and subsequently a new pod, which can cause a lot of confusion and headache. But it is more likely that the PVC is the redeployment of the original application and so you want it to be reassigned to the new PVC.&lt;br /&gt;
{{Note|text=If you delete an application, you MUST delete the PV manually when its reclaim policy is &amp;quot;Retain&amp;quot;, otherwise the data will never get deleted and could be reassigned to another claim.|type=warn}}&lt;br /&gt;
&lt;br /&gt;
===== Other volumes =====&lt;br /&gt;
Persistant volumes are probably the easiest to understand, but there are a lot of other types of volumes, most notably empty directorys and ephemoral volumes as well as volumes creating from config maps or secrets. All of these serve different purposes:&lt;br /&gt;
&lt;br /&gt;
* Empty directory - something like &amp;lt;code&amp;gt;/tmp&amp;lt;/code&amp;gt;, all data will be stored in RAM if set to writable and will be deleted when the pod restarts or is deleted. You should set a limit to how much RAM is allowed to be stored there, as it could soak up the full resources of the computer.&lt;br /&gt;
* [https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/ Emphemeral volumes] - similar to empty directorys, but instead of being stored in RAM, they are stored on disk (through persistant volumes) and will be deleted upon deletion of the pod and so mostly not really useful unless you are expecting to generate a lot of data during the running of the pod and don&#039;t want to retain them on reboot.&lt;br /&gt;
* Mounting secrets - this is read only storage and just allows you to mount configuration files that store database passwords or similar. Each key within the secret becomes a text file. Note that if its a binary file (e.g. image) you can use the &amp;lt;code&amp;gt;binaryData&amp;lt;/code&amp;gt; property and pass in a hex string.&lt;br /&gt;
* Mounting config maps - similar to secrets, but just are for files that don&#039;t have to be secret and encrypted on the machine.&lt;br /&gt;
&lt;br /&gt;
=== Cronjobs ===&lt;br /&gt;
&lt;br /&gt;
=== K3S ===&lt;br /&gt;
&lt;br /&gt;
== Deployment with Tofu ==&lt;br /&gt;
Information pertaining to terraform configuration itself can be found on [[Terraform|the Terraform page]] and for specific how to deploy to BOSS&#039; production and staging cluster, please see the [https://gitlab.bath.ac.uk/cs/int/terraform project&#039;s README] and [https://gitlab.bath.ac.uk/cs/boss/int-wiki internal wiki] for more information. This will mostly focus on the general process of deploying with tofu and the struggles.&lt;br /&gt;
&lt;br /&gt;
Please read the [[Kubernetes#K9s|K9s section]] for information about using k9s for monitoring deployments.&lt;br /&gt;
&lt;br /&gt;
Some resources may also have their own special command set to allow you to perform resource specific actions which is nice (these should be listed at the top right).&lt;br /&gt;
&lt;br /&gt;
Back to deployment, it&#039;s mostly:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu apply -var-file=./prod.tfvars [-target=module.something]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Using &amp;lt;code&amp;gt;-target&amp;lt;/code&amp;gt; can help a lot when the terraform module is huge as it will limit the number of resources it has to check for updates (though this means that you state and configuration can become desynced and so you should still do apply&#039;s without the targetting). You can even specify the exact resource you have just edited for super fast (for terraform) iterations.&lt;br /&gt;
&lt;br /&gt;
While it&#039;s deploying you cannot hit &amp;lt;code&amp;gt;Ctrl-c&amp;lt;/code&amp;gt; or well you can (twice) to force exit the application, but this can result in annoying consequencies:&lt;br /&gt;
&lt;br /&gt;
* Hitting it before you&#039;ve confirmed the plan (e.g. you accidentally forgot to include target and don&#039;t want to wait): The state &#039;&#039;&#039;WILL&#039;&#039;&#039; be locked and will not be unlocked. Therefore you have to run&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu force-unlock &amp;lt;uid&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where the &amp;lt;code&amp;gt;&amp;lt;uid&amp;gt;&amp;lt;/code&amp;gt; can be found by trying to run the apply command and it failing.&lt;br /&gt;
&lt;br /&gt;
* Hitting it while it&#039;s deploying new resources (e.g. the pods are not deploying and you didn&#039;t change the 5 minute timeout): on next run, tofu will try to recreate those resources, and so before running the command, you must open k9s, find the resources and delete them.&lt;br /&gt;
* Hitting it while updating helm charts (e.g. you didn&#039;t change the 10 minute timeout and it&#039;s not working): on the next run, tofu will refuse to deploy it, because the helm deployment is in an invalid state (&amp;quot;deploying&amp;quot;) and it will never exit this state. Therefore you must open up &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; type &amp;lt;code&amp;gt;helm &amp;lt;namespace&amp;gt;&amp;lt;/code&amp;gt;, wait a year, click &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; on the effected resource to list all the previous releases, go down to the last successful deployment and click &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; again to rollback to that release, and again wait a year while k9s refuses to respond.&lt;br /&gt;
&lt;br /&gt;
As you can probably tell deployment takes patience, expecially when you don&#039;t alter the timeouts in helm (which default to 10 minutes for creation and 10 minutes for deletion). You don&#039;t want to interrupt the flow while tofu is doing it&#039;s thing, so make sure you aren&#039;t going to have to wait 10 minutes because you made a simple typo causing the pods to crash loop.&lt;br /&gt;
&lt;br /&gt;
You can technically, temporarily edit the resources (with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; in k9s) to fix your mistake and to make it successfully deploy, so you can actually fix the mistake and redeploy (updating all the resources again) within a shorter time than it takes for tofu to timeout. You do want to be careful with timeouts however, as if you are on poor wifi (e.g. a train) or if the cluster is a bit pinned atm, deployments will take longer and the worst thing is if tofu times out but the deployment actually succeeded (though just reapply should update the state without an actualy redeploymenht unless its helm).&lt;br /&gt;
&lt;br /&gt;
=== Retaining PVs ===&lt;br /&gt;
As previously mentioned, whenever deploying new applications, you should mark any persistant volume that stores data you do not want accidentally deleted with a &amp;quot;Retain&amp;quot; reclaim policy. This can easily be done using &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; by typing &amp;lt;code&amp;gt;:pv&amp;lt;/code&amp;gt;, finding the pv attached to the claim, pressing &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; and searching for where the &amp;lt;code&amp;gt;reclaimPolicy&amp;lt;/code&amp;gt; is defined. By default it will be &amp;quot;Delete&amp;quot;, and you can just replace it with &amp;quot;Retain&amp;quot;. &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; will also show you a PV&#039;s reclaim policy on the list view which is nice to check if its already been done.&lt;br /&gt;
&lt;br /&gt;
=== Where is the state stored? ===&lt;br /&gt;
{{Note|text=State files store all passwords and secrets generated or collected by terraform unencrypted by default and so you should be extremely cautious with where the files are stored, who has access to them and never commit them.|type=warn}}&lt;br /&gt;
Usually, if you don&#039;t define any backends, it is stored in a &amp;lt;code&amp;gt;.tfstate&amp;lt;/code&amp;gt; file within the directory of your folder. If you are just managing your cluster with them, an easy place to store it is within the kubernetes cluster itself, using the kubernetes backend:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
terraform {&lt;br /&gt;
  # ...&lt;br /&gt;
&lt;br /&gt;
  backend &amp;quot;kubernetes&amp;quot; {&lt;br /&gt;
    secret_suffix  = &amp;quot;my-cluster&amp;quot;&lt;br /&gt;
    config_paths   = [var.kube_config_path]&lt;br /&gt;
    config_context = var.kube_context&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  # ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This is BOSS&#039;s method of storage as we do not have to trust the GitLab instance with our cluster&#039;s life. But others can be chosen, for example [https://docs.gitlab.com/user/infrastructure/iac/terraform_state/ within GitLab itself], although this should be done with extreme caution due to the risk of exposing plan files (which contain secrets) to the world.&lt;br /&gt;
&lt;br /&gt;
=== Managing the state ===&lt;br /&gt;
As we can never write perfect code the first time round, some MRs will result in refactorisation of components into new modules. Therefore state management is important. This annoyingly is painful especially with larger effected packages and so I would recommend considering the effects of destroying all the effected resources and reapplying them (which is what  &amp;lt;code&amp;gt;tofu apply ...&amp;lt;/code&amp;gt; will do by default). If you cannot afford this (e.g. with persistant volumes) you will have to use &amp;lt;code&amp;gt;tofu state mv&amp;lt;/code&amp;gt; which works by moving the specified address to another (make sure there is no typos though!&lt;br /&gt;
&lt;br /&gt;
However this requires the resource type to be the same through the move, which sometimes may not happen. At which point you&#039;ll want to delete the state information and re-import it. E.g.&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu state rm module.example.my_resource.name&lt;br /&gt;
# Import the module (Note: the format will differ depending on the provider)&lt;br /&gt;
tofu import module.example.module.refractorisation.my_new_resource.name my_resource/name&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Updating packages ===&lt;br /&gt;
To make upgrading easier, in submodules, packages are pinned to the nearest major version. You can then update the base package version and run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu init -upgrade&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Which will update the package versions on your machine and update the lock file.&lt;br /&gt;
&lt;br /&gt;
If you do not specify &amp;lt;code&amp;gt;-upgrade&amp;lt;/code&amp;gt; it will just update the module list (e.g. if you add a new application with a new use of &amp;lt;code&amp;gt;module&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
== K9s ==&lt;br /&gt;
[https://k9scli.io/ k9s] is recommended for watching deployments and seeing why they failed as its generally just fantastic the more you get used to it. The common shortcuts you need to know are:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; will describe the currently selected object. If you go to the bottom (&amp;lt;code&amp;gt;Shift-G&amp;lt;/code&amp;gt;) you will be able to see events associated with that resource, for pods and statefulsets this is &#039;&#039;&#039;extremely useful&#039;&#039;&#039; as it includes failures for pulling images or security denials.&lt;br /&gt;
* &amp;lt;code&amp;gt;l&amp;lt;/code&amp;gt; see the logs of all containers associated with the resource.&lt;br /&gt;
* &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; while within a secret/configmap will show you the raw (string) data&lt;br /&gt;
* &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; to shell into a pod (note that some pods do not support this as they do not ship withsh&lt;br /&gt;
* &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; allows you to perform a rolling restart on a statefulset or deployment (meaning there should be no downtime if everything is configured). This is the recommended way to update a pod on production (&#039;&#039;&#039;do not&#039;&#039;&#039; just delete the pod itself).&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;enter&amp;gt;&amp;lt;/code&amp;gt; see any subresources (e.g. containers for pods, or pods for deployments/statefulsets)&lt;br /&gt;
* &amp;lt;code&amp;gt;Ctrl-d&amp;lt;/code&amp;gt; allows you to delete the resource&lt;br /&gt;
* &amp;lt;code&amp;gt;Ctrl-f&amp;lt;/code&amp;gt; allows you to add a port-forward to your own machine (really useful for debugging if an application is responding but is not available via traefik), or getting direct access to databases.&lt;br /&gt;
* &amp;lt;code&amp;gt;:&amp;lt;resource&amp;gt; &amp;lt;namespace|all&amp;gt;&amp;lt;/code&amp;gt; will change the list view to the given resource in the namespace&lt;br /&gt;
* &amp;lt;code&amp;gt;:&amp;lt;resource&amp;gt;&amp;lt;/code&amp;gt; will change the list view for the given resource in the currently selected namespace&lt;br /&gt;
* &amp;lt;code&amp;gt;0-9&amp;lt;/code&amp;gt; are shortcuts to switch between recently selected namespaces (they should show at the top with the current assignment)&lt;br /&gt;
&lt;br /&gt;
== Migrating storages ==&lt;br /&gt;
When you are migrating clusters to a new machine (just copy the VM though) or just moving statefulsets between namespaces (persistant volumes are namespace agnostic though), you may need to migrate persistant volumes. As I have done this multiple times now, and so here is my steps to do so:&lt;br /&gt;
&lt;br /&gt;
=== Is it a database? ===&lt;br /&gt;
Databases have users and passwords associated, which can cause issues if you are redeploying a whole cluster with terraform as new passwords will be generated. You can import new values but that&#039;s boring. Instead you can just us &amp;lt;code&amp;gt;pg_dump&amp;lt;/code&amp;gt; (or equivalent):&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl port-forward -n my_namespace service/my_pod 5432:5432 &amp;amp;&lt;br /&gt;
pg_dump $DATABASE_URL &amp;gt; my_pod_bkp.sql&lt;br /&gt;
killall kubectl # Stop port-forward&lt;br /&gt;
# ...&lt;br /&gt;
# Apply the SQL to the volume in the new cluster:&lt;br /&gt;
kubectl --context new_cluster port-forward -n my_namespace service/my_port 5432:5432 &amp;amp;&lt;br /&gt;
psql -U username -d myDataBase -a -f my_pod_bkp.sql&lt;br /&gt;
killall kubectl # Stop port-forward&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Multinode clusters ==&lt;br /&gt;
[[Bath Open Source Society|BOSS]] has specifically chosen not to use multinode clusters and so this is just for the interested people, and hopefully explain why we did not go down this route. Yes, multinode clusters are the whole point of kubernetes, where the program chooses the at least somewhat optimal location to deploy an application and automatically move it to another node if the one its on crashes or restarts for an update. But there are also benefits of kubernetes outside of this feature, such as proper permission system as well as providing a somewhat clean interface for managing deployments.&lt;br /&gt;
&lt;br /&gt;
Additionally, we felt that as a lot of companies are moving towards managing deployments, at least partially, with kubernetes, our use of it will hopefully provide easy place for students to experiment and learn about it (as this is stuff university will never teach you). It should be noted that if companies actually cared about getting the most &amp;quot;bang for your buck&amp;quot;, it is worth more to manage deployments yourself and choose which nodes host them, though this can produce more overhead.&lt;br /&gt;
&lt;br /&gt;
=== Benefits ===&lt;br /&gt;
There are clear benefits of running a multinode cluster, node down? That&#039;s fine, all applications can still run just fine and are accessible. Updating your entrypoint? That&#039;s fine, just point your assumingly IPv4 to another node, or if you&#039;re using IPv6 just setup DNS to point to both nodes. With my multinode cluster, I am writing this currently after one node completely bricked itself and the other node down after I completely bricked it and all applications are still running fine on the three remaining nodes.&lt;br /&gt;
&lt;br /&gt;
=== Downsides ===&lt;br /&gt;
With all this new power comes significant overhead though. For one, if a node goes down its not always as simple as kubernetes automatically moving things. Firstly, volumes may only be stored on that one node, then they may only be locked to that one node with &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;. So if the machine didn&#039;t shutdown properly (or if one just get&#039;s disconnected from the control plane) you will have to make intervention steps as soon as possible, though, you don&#039;t have to be in a panic to get the node itself back up. If you have correctly [[Kubernetes#Safely shutting down nodes|setup safe shutdowns]] and its a routine update though, you probably don&#039;t need to touch it.&lt;br /&gt;
&lt;br /&gt;
Power, is another one, the more nodes, the more your [[Kubernetes#Control plane|control plane]] has to manage, meaning the more its going to be stuck managing the cluster, and so will not be able to run their own pods. This means that going from a single node cluster to a multinode cluster, you will probably need at least 3-4 additional machines for it to be managed and working properly. Yes you could run a 2 or 3 node cluster, but the benefits really are not there for the massive amount of downtime. On top of this, you probably want at least 1 node of wiggleroom capable of handling your highest power node, this means that you can never reach the full potential of your cluster, as both your control plane and your other nodes must be able to take the strain when that node is disconnected.&lt;br /&gt;
&lt;br /&gt;
Storage is probably the largest issue that you will need to tackle, and is the one that put BOSS of going this route (along with the fact that we wanted to keep some of our servers as hot spares if parts broke). This will be explored more in the [[Kubernetes#How to manage Storage|&amp;quot;How to manage Storage&amp;quot; section]], but the summary is, although there are tools out there, they require a minimum node count of 3 and have significant overhead. Additionally, because of the potential constant movement of data between nodes going down (even for general updates), it causes additional wear and tear on your drives, reducing their life expectancy (I killed a brand-new SSD in 6 months seemingly because my nodes were going down for updates so often). &lt;br /&gt;
&lt;br /&gt;
Networking is a minor one, all nodes need to be connected to control plane and cannot get randomly interrupted or cut off (e.g. you have your control plane in one room hooked up to a UPS and a agent node in another on a UPS, but the router that connects them is not on the UPS). Disconnection at critical times can cause the control node to start moving workloads off the disconnected node onto others, which with statefulsets or &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt; volumes can cause desynchronisation which is a massive issue and will cause corruption. Finally there is just a minor issue that if you don&#039;t setup correctly you will probably be quite confused, Traefik forwarding going between nodes may result in the final application being given the wrong IP address.&lt;br /&gt;
&lt;br /&gt;
Finally, there is also a bit of a weird one, managing SELinux policies and kernel modules across the cluster. I have had multiple issues in the past of minor configuration differences between my nodes resulting in a pod crashlooping on one node and absolutely just fine on another. Therefore this creates additional overhead as you then have to make sure all installs are identical and then potentially install the security profile operator (which is great when it works but not so great when the docker container they use is ancient and doesn&#039;t work on the latest systems and k3s accidentally breaks support for it). &lt;br /&gt;
&lt;br /&gt;
And so, in summary, multinode clusters are amazing for chaising that 99.99999999999% uptime and really interesting for learning the difficulties with it and all the solutions out there. But it is really not for the faint of heart, I would recommend trying with a bunch of VMs on a single node just to understand how it works, but if you are just a hobbiest wanting to host some basic applications at home, do not use it. I am planning on deconstructing my cluster and replacing it with a single node similar to [[Bath Open Source Society|BOSS]] after I leave univesity.&lt;br /&gt;
&lt;br /&gt;
=== Control plane ===&lt;br /&gt;
The first challenge is the control plane. What&#039;s the benefit of a multinode cluster when it relys on a single node for sending control signals. If it goes down, the whole cluster will panic if a pod crashes, it is left in a seriously vulnerable position. Therefore, you instead need to expand your control plane to multiple nodes. K3s offers built in solution for this, [https://docs.k3s.io/datastore/ha-embedded?_highlight=etcd utilising &amp;lt;code&amp;gt;etcd&amp;lt;/code&amp;gt;] by default to share the cluster state between control nodes, or you can just move it off to [https://docs.k3s.io/datastore/ha a separate database cluster] (which is what&#039;s recommended in production as you probably have another spare 3 machines). Then you must setup a load balancer between the control nodes (which actually helps with not overloading a single control node). But now the issue is that you are relying on a single load balancer. As a hacky solution, I setup a load balancer on all my control nodes which balanced the load between itself and the other nodes. This mean that the DNS record could store all IPs associated with the nodes and hopefully mitigate any communication issues when a single one went down.&lt;br /&gt;
&lt;br /&gt;
However, when setting up etcd, you must remember to point the &amp;lt;code&amp;gt;--server&amp;lt;/code&amp;gt; to the load balancer and not a single node (as then the single node becomes the single point of failure).&lt;br /&gt;
&lt;br /&gt;
=== How to manage Storage ===&lt;br /&gt;
I personally used [https://longhorn.io/ longhorn], which provided a somewhat easy solution, you just have to change the default storage class and migrate all your PVs. It requires that you have 3 nodes to share data between (as it can then use voting logic to ward off corruption). By default, it then stores volumes on three different nodes at the same time, meaning if one node goes down, it will just make a copy to a new node with the remaining two copies. Additionally, if a pod is assigned to a node without a copy of the data, longhorn can simply make a new copy onto that node (however it is more likely that kubernetes will choose a node that already has the storage).&lt;br /&gt;
&lt;br /&gt;
It then provides a UI where you can easily manually update all the storage volumes after an update of longhorn and also manage automated backups and snapshots. It is actually a really handy tool and generally really cool (though you have to manually label PVs with the types of backups you want).&lt;br /&gt;
&lt;br /&gt;
The issue? It uses around 2GiB of RAM on each individual node. Then if your nodes are constantly going down (e.g. automated updates every week), it will instantly start panicking and moving volumes over which can wear out drives. Additionally, if too many nodes go down, it can be the single source of crashing everything else because of the amount of resources it can use.&lt;br /&gt;
&lt;br /&gt;
=== Safely shutting down nodes ===&lt;br /&gt;
This is basically a requirement if you actually want to obtain the full benefits of multinode cluster that requires as little manual intervention as possible (which is still somehow more intervention than a single node cluster IMO). There is a great [https://oranki.net/posts/2025-01-09-graceful-k3s-shutdown/ blog on &amp;lt;code&amp;gt;oranki.net&amp;lt;/code&amp;gt;] on how to set this up, but effectively you need to set that anytime the &amp;lt;code&amp;gt;k3s&amp;lt;/code&amp;gt; service is stopped you run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl drain --ignore-daemonsets --delete-emptydir-data &amp;lt;node&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Then when it comes back up you need to run:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
kubectl uncordon &amp;lt;node&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;As explained in the blog post, you can do this via a systemctl service.&lt;br /&gt;
&lt;br /&gt;
What this does is makes sure all nodes are safety migrated onto another node before a shutdown can take place, effectively tainting the node (meaning can be assigned to it). Then you must &amp;lt;code&amp;gt;uncordon&amp;lt;/code&amp;gt; a node (meaning removing the taint) to tell the control plane that you can now place pods on it again.&lt;br /&gt;
&lt;br /&gt;
== Backups ==&lt;br /&gt;
&lt;br /&gt;
== Alerting ==&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Kubernetes event log ===&lt;br /&gt;
&lt;br /&gt;
=== Traefik dashboard ===&lt;br /&gt;
&lt;br /&gt;
=== Shell-ing into pods ===&lt;br /&gt;
&lt;br /&gt;
=== K3S Log ===&lt;br /&gt;
&lt;br /&gt;
=== Emergency Debug pods ===&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare API tokens ===&lt;br /&gt;
&lt;br /&gt;
=== SELinux ===&lt;br /&gt;
&lt;br /&gt;
=== Tofu ===&lt;br /&gt;
&lt;br /&gt;
=== Scenarios ===&lt;br /&gt;
&lt;br /&gt;
==== Deployment/statefulset created but pod not creating ====&lt;br /&gt;
&lt;br /&gt;
==== Pod in cash loop ====&lt;br /&gt;
&lt;br /&gt;
==== Pod cannot request any website ====&lt;br /&gt;
&lt;br /&gt;
==== Pod can access the internet but everything returns self-signed certificate ====&lt;br /&gt;
&lt;br /&gt;
==== Deployment can&#039;t access its database/valkey ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is requesting denied system privileges ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 404 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 403 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Authelia refuses to start up ====&lt;br /&gt;
&lt;br /&gt;
==== Node is crashing often after running out of RAM ====&lt;br /&gt;
&lt;br /&gt;
==== On IPV6 cluster and requests randomly timeout or return 404 ====&lt;br /&gt;
&lt;br /&gt;
==== Statefulset refusing to start pod (PVC) ====&lt;br /&gt;
&lt;br /&gt;
==== A node just crashed and went offline ====&lt;br /&gt;
&lt;br /&gt;
=== Cleaning up ===&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=User:Hw2210&amp;diff=145</id>
		<title>User:Hw2210</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=User:Hw2210&amp;diff=145"/>
		<updated>2026-06-05T18:50:30Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Some basic text I guess&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I am Hugo, ex-Computer Science student and am the initial fouder of bathcs.com along with Pal Kerecsenyi and Penn Mackintosh and previously hosted froom and other projects on my homelab. [[Bath Open Source Society|BOSS]] was founded to replace me and have a sustainable hosting.&lt;br /&gt;
&lt;br /&gt;
Initially the predecessor to BOSS was founded to provide a coordinated resources for the expanding number of computer science soceities as well as provide a service for storing your password (which is where [[VaultTub]] was born). From this we created the [https://gitlab.bath.ac.uk/cs &amp;quot;cs&amp;quot; organisation on Bath&#039;s GitLab] to store all code written so we can share common creations (such as the peer mentor website). Then we created a wiki to provide information about hosting stuff for free.&lt;br /&gt;
&lt;br /&gt;
== Links ==&lt;br /&gt;
&lt;br /&gt;
* LinkedIn: [https://www.linkedin.com/in/hugo-whittome-69053924a/ Hugo Whittome]&lt;br /&gt;
* Website: [https://wilfsilver.co.uk wilfsilver.co.uk]&lt;br /&gt;
* Mastodon: [https://mstdn.social/@wilfsilver @wilfsilver@mstdn.social]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=144</id>
		<title>Kubernetes</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=144"/>
		<updated>2026-06-05T17:37:22Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Work on writing the deployment section.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I ([[User:Hw2210|hw2210]]) have been asked to write down my processes for working with kubernetes and terraform as I am leaving this year, and hopefully it will serve as useful information to any future sysadmin. This page is the output of this and is written to be as generic as possible, so if you are just experimenting with kubernetes on a home lab, please feel free to read and hopefully you will learn somethings. There will be multiple references to [[BOSS/Hosting/Cluster|BOSS&#039;s cluster]], which has all the security boxes ticked on and so we have to deal with security contexts, network policies and [[SELinux]] as they are the bain of all problems.&lt;br /&gt;
&lt;br /&gt;
== General knowledge ==&lt;br /&gt;
This tries to cover some basic concepts, focusing on common confusion, but it will skip over a lot of the general knowledge information such as secrets and configmaps. The kubernete&#039;s documentation is pretty good, though difficult to read at some points, but there are loads of great tutorials explaining how kubernetes works.&lt;br /&gt;
&lt;br /&gt;
=== Pod vs Container ===&lt;br /&gt;
A common confusion is that [https://kubernetes.io/docs/concepts/workloads/pods/ pod]&#039;s are containers in kubernetes. This is not exactly true, a pod is a general group of linux namespaces which can host multiple containers. This means you can have a container that writes to a directory and another container that reads from that directory in the same pod. This can be very powerful, but in a lot of cases can be ignored.&lt;br /&gt;
&lt;br /&gt;
But it is key to point out that a Pod is a resource that is created by other kubernetes resources. They are a group of processes running, once they die the pod is deleted and forgotten about. Therefore you should not be creating pods directly, instead you should be using deployments, statefulsets, cronjobs or even jobs. All these resources create generate a pod as their lifecycle and will restart/recreate the pod if it fails.&lt;br /&gt;
&lt;br /&gt;
=== Statefulset vs deployment ===&lt;br /&gt;
Another key understanding is the difference between statefulsets and deployments, as statefulsets can cause some confusion in how they work. The difference is more applicable to multinode clusters but are still key to the structure of kubernetes.&lt;br /&gt;
&lt;br /&gt;
Effectively, a statefulset is a deployment with writable volumes - known as persistent volumes (PV). Having the ability to write to volumes can cause race conditions when multiple pods across nodes are writing to the same file. This is where statefulsets come in, they lock volumes and so they can only be used by one node and one pod, with scaling creating new persistant volumes which are stored separately. This means that if you scale a statefulset that relies on shared knowledge in the volume, half your requests will have one set of data and the other half will have another.&lt;br /&gt;
&lt;br /&gt;
This obviously is quite a big disadvantage and can lead to confusing behaviour when a node is not configured to shutdown safely and taint itself, moving all the statefulsets off of itself before it shutsdown - if PV is locked by a node and pod, it cannot be deployed to another cluster.&lt;br /&gt;
&lt;br /&gt;
Therefore, this is where deployments come in, they, usually, do not have associated persistent volumes, allowing for easy horizontal scaling. For storing shared data, they should connect to a database on another node which can be more compatible with statefulsets when configured correctly.&lt;br /&gt;
&lt;br /&gt;
Both of these resources will create pods and redeploy them if they crash.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=If you update any &amp;quot;volume&amp;quot; attribute within a statefulset ALL volumes will be deleted and recreated. To not lose any data, please make sure that the PV&#039;s reclaim policy has been set to &amp;quot;Retain&amp;quot;. You should generally do this for any data you do not want to lose.|type=warn}}&lt;br /&gt;
&lt;br /&gt;
==== Liveness/Startup probes ====&lt;br /&gt;
Liveness and startup probes can be defined on pods, and these let kubernetes know if a pod has started correctly and if it still is alive. For example, some deployments might take a while to start up and configure everything before it starts serving content and so when restarting, this can cause some downtime. Downtime is what we are trying to avoid and so by using a startup probe, kubernetes knows that this application is ready, and so it will only terminate the previous node once the new one is started up resulting in zero downtime!&lt;br /&gt;
&lt;br /&gt;
The liveness probe on the other hand periodically checks whether the pod is still alive. This means that if it suddenly stops responding due to a long database query, kubernetes can detect that and replace the pod with another further reducing downtime. However, this usually suggests something else is wrong with the application and so this should be investigated and fixed.&lt;br /&gt;
&lt;br /&gt;
==== Security Context ====&lt;br /&gt;
{{Note|text=Within [[BOSS/Hosting/Cluster|BOSS&#039;s kubernetes cluster]], we define a security policy which requires all pods to correctly define their security context and make sure that it is not running as root.}}&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ security context] defines what privileges the pod has when running, we effectively want this to be as minimal as possible to reduce attack surface area. E.g.&lt;br /&gt;
&lt;br /&gt;
* Run as user&lt;br /&gt;
* Don&#039;t allow privilege escalation&lt;br /&gt;
* Properly define seccomp policy&lt;br /&gt;
* Default SELinux container context&lt;br /&gt;
* Drop all capabilities&lt;br /&gt;
&lt;br /&gt;
However this can cause issues with third-party applications which commonly do some questionable things, e.g. require running as root or changing the uid. But for our pods you can mostly just copy and paste:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  # ...&lt;br /&gt;
  spec {&lt;br /&gt;
    # ...&lt;br /&gt;
    template {&lt;br /&gt;
      # ...&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          # ...&lt;br /&gt;
&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          # ...&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[Terraform]] for more information.&lt;br /&gt;
&lt;br /&gt;
=== What is a CRD? ===&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ Custom Resource Definition (CRD)], allows you to extend kubernetes capabilities and define custome resources. This is usually paired with an operator which reads the resources and performs some actions.&lt;br /&gt;
&lt;br /&gt;
We should never create our own, but third-party ones make it much easier for doing things such as creating ingress routes with traefik or define database clusters with our postgres operator.&lt;br /&gt;
&lt;br /&gt;
K9s and kubectl support these out of the box (as they are basically just schemas for yaml configuration), and you can see all pods by using the name of the resource.&lt;br /&gt;
&lt;br /&gt;
=== Traefik and gateways ===&lt;br /&gt;
[[File:Gateway diagram.svg|thumb|368x368px|A digram depicting the the flow of traffic from the internet, to traefik then to each namespace&#039;s gateway. Each gateway then looks at all connected certificates to add TLS authentication and then looks at the HTTPRoute to find which one matches based on the rules which then defines what service to forward the traffic to and subsequently the pods.]]&lt;br /&gt;
Kubernetes works by defining services, which give a common endpoint to call potentially multiple pods. These can then be exposed through HTTPRoutes and the [https://kubernetes.io/docs/concepts/services-networking/gateway/ Gateway API], in which traefik implements.&lt;br /&gt;
&lt;br /&gt;
The Gateway API resources are read by [https://doc.traefik.io/traefik/ traefik], which acts as the implementation, and acts accordingly to the defined configuration. Therefore, in essence, the gateways act only as a means for configuring traefik. But effectively, traefik has configured open ports it can expose, it then looks for Gateways, in the permitted namespaces, for their configuration. The gateways stores a list of ports that the namespace can expose (though it cannot add one that is not included within traefik configuration itself), as well as a list of certificates. At this point, the domain requested must have a certificate configured within the gateway, and all TLS logic is handled by traefik and so all further traefik is effectively decrypted. Notice here that if a certificate is not configured on the gateway, it cannot be served (one of the downsides of the gateway API).&lt;br /&gt;
&lt;br /&gt;
For us, we have decided to have each namespace have their own gateway, due to the protections traefik offers, this means that we do not have to do any cross namespace references for certificates, and do not have to update the main gateway anytime we need to add a certificate. There is an additional issue with this, is that during the time the certificate doesn&#039;t exist but is configured (e.g. when first request it), the gateway is deemed invalid and so doesn&#039;t route any traefik (even http). There is a plan to help mitigate this through the use of &amp;lt;code&amp;gt;ListenerSets&amp;lt;/code&amp;gt; but this is yet to be supported in traefik and still has this issue. Therefore, we want to make sure that a single gateway hosts services for as few applications as possible (preferably only one).&lt;br /&gt;
&lt;br /&gt;
Anyway, the gateway will have a number of child routes (&amp;lt;code&amp;gt;TLSRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HTTPRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GRPCRoutes&amp;lt;/code&amp;gt; and coming in the future &amp;lt;code&amp;gt;TCPRoute&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;UDPRoute&amp;lt;/code&amp;gt;). These routes act as queries to determine when and what traefik should forward to. So for example they act as:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
if hostname is example.bathcs.com forward to example-service&lt;br /&gt;
if the path starts with /api forward to api-service&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Then traefik can request those services, allowing kubernetes to effectively take over, looking at the pods associated with the service and using the defined algorithm to send the request to those pods and using the defined ports.&lt;br /&gt;
&lt;br /&gt;
This does mean there are a number of different places a port can change:&lt;br /&gt;
 Exposed port -&amp;gt; Traefik internal port -&amp;gt; Service port -&amp;gt; Pod port -&amp;gt; Application port&lt;br /&gt;
In most cases you should have the service port, pod port and application port all matching, this makes debugging a lot easier. Additionally there are few reasons why you will want to change traefik&#039;s internal port and the exposed port (but there are some!).&lt;br /&gt;
&lt;br /&gt;
==== Certificates ====&lt;br /&gt;
The thing with certificates is that we effectively never want to manually create them, the recommended expiry time for certificates is always dropping, with the most recent update at 45 days. This is way too much work for manual requesting and uploading and adds too many layers for it to go wrong. Therefore we use cert manager, which allows defining certificate objects within the cluster, cert manager will then go and do all the requesting for us and store it in a secret. Then it will also track the expiry and automatically update the certificate a week or so before it expires.&lt;br /&gt;
&lt;br /&gt;
There are multiple different methods it can use to validate that we are in fact in charge of the domain:&lt;br /&gt;
&lt;br /&gt;
* DNS - this is the preferred method, as it allows us generating certificates for protected IPs. But this requires a valid cloudflare API token (which is restricted to a single IP).&lt;br /&gt;
* HTTP - this is when the certificate authority will request our server from multiple locations, which means the DNS cannot be set to a protected IP. But it means we can generate certificates for domains that we don&#039;t control the DNS of (e.g. bath.ac.uk hostnames) but it is at least configured to point to our server. The integration with traefik means that there is no additional work required for the application to get these working.&lt;br /&gt;
* Cloudflare Origin - These are very special certificates and cannot be decrypted by the browser. The idea is that by generating these certificates, only cloudflare themselves will be able to decrypt the contents and so only they can proxy your IP. We use this specifically for [[Kubernetes#Cloudflare proxy|Cloudflare proxy]]-ing thought it doesn&#039;t provide us the true benefits (given our IP is still public)&lt;br /&gt;
&lt;br /&gt;
Within cert manager&#039;s speak, these are known as issuers, and we have cluster issuers defined for each (meaning any namespace in the cluster can use them).&lt;br /&gt;
{{Note|text=When cert manager is first requesting the certificate, the configured gateway will be invalid and so no routes attached will forward traffic.|type=reminder}}&lt;br /&gt;
&lt;br /&gt;
==== Cloudflare proxy ====&lt;br /&gt;
Cloudflare proxy offers the benefits of caching our content on &amp;quot;edge&amp;quot; servers, meaning that our websites perform much better on average as well as it can protect the IP of the machine, but as explained later we don&#039;t use cloudflare proxy everywhere and so lose this advantage. This caching is amazing when the application is configured for it to work well with it (e.g. correctly labelling requests as cachable). But it does not work with every application, especially third-party services which sometimes just break when using it. But it also adds troubling security questions, for example, a login page will also be proxied, and decrypted by cloudflare, resulting in cloudflare having access to all passwords that go through the site. For this reason we limit where we use cloudflare proxying to services that would benefit heavily from it (e.g. this Wiki as authentication is handled offsite).&lt;br /&gt;
&lt;br /&gt;
To setup cloudflare proxying, it is as simple as generating a certificate with the cloudflare origin issuer and exposing a HTTPRoute with the certificate and then enabling proxy in the dns record. Obviously this does not work with internal DNS records (e.g. &amp;lt;code&amp;gt;k8s.bathcs.com&amp;lt;/code&amp;gt;) and so our terraform config automatically detects and does not proxy this stuff.&lt;br /&gt;
&lt;br /&gt;
The cloudflare origin issuer then speaks to the cloudflare origin operator which requests a certificate from cloudflare themselves. The generated certificates can be found in the cloudflare dashboard for the domain under &amp;quot;SSL/TLS &amp;gt; Origin Server&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Network Policies ===&lt;br /&gt;
With additional security, comes additional policy managment. [https://kubernetes.io/docs/concepts/services-networking/network-policies/ Network policies] tell kubernetes where a pod is allows to send and receive traffic. However they are a bit confusing at times, and so can cause some headache when trying to debug why your pod cannot communicate with your database.&lt;br /&gt;
&lt;br /&gt;
* By default ALL outgoing traffic is allows.&lt;br /&gt;
* By default NO incoming traffic is allows (except from traefik).&lt;br /&gt;
&lt;br /&gt;
So by default, any pod is allows to communicate with any port on the internet, but not allows to communicate with any other pod in the whole cluster. In the futher we hope to disallow both by default, and so you will have to specify exactly what ports (and potentially where) your pod should be communicating.&lt;br /&gt;
&lt;br /&gt;
Within the terraform, we have helper functions built into the utilities for the databases to automatically generate network policies for incoming traffic, relying on the requirement of adding a label to your pod, they are usually outputted by the module under &amp;lt;code&amp;gt;client_labels&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
To define your own policy (in terraform ofc), there are two parts &amp;lt;code&amp;gt;ingress&amp;lt;/code&amp;gt; (incoming traffic) and &amp;lt;code&amp;gt;egress&amp;lt;/code&amp;gt; (outgoing traffic). Both of these can then be a list of rules matching pods that will be allows. They are additive, meaning that all network policies matching the pod will be combined to produce the final ruleset.&lt;br /&gt;
&lt;br /&gt;
The main rules you will want to focus on are:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;ip_block&amp;lt;/code&amp;gt; defines a lock of IPs where the traffic originates or is going to&lt;br /&gt;
* &amp;lt;code&amp;gt;namespaceSelector&amp;lt;/code&amp;gt; defines the labels matching on the namespaces where traffic is allows to/from&lt;br /&gt;
* &amp;lt;code&amp;gt;podSelector&amp;lt;/code&amp;gt; same as namespaces but specific to pods themselves (e.g. what our databases do)&lt;br /&gt;
* &amp;lt;code&amp;gt;ports&amp;lt;/code&amp;gt; defines the ports allows by these connections&lt;br /&gt;
&lt;br /&gt;
While defining any egress policies you must remember to include basic services, e.g. DNS and maybe NTP. An example full configuration might look like:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_network_policy_v1&amp;quot; &amp;quot;example&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;my-policy&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    egress {&lt;br /&gt;
      # Allow pod to use DNS&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 53&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 53&lt;br /&gt;
        protocol = &amp;quot;UDP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      # Allow pod to request its database&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = 5432&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    ingress {&lt;br /&gt;
      # Allow pods within namespaces that have enabled ldap (with the defined&lt;br /&gt;
      # label) to request this pod via the &amp;quot;ldap&amp;quot; port&lt;br /&gt;
      ports {&lt;br /&gt;
        port     = &amp;quot;ldap&amp;quot;&lt;br /&gt;
        protocol = &amp;quot;TCP&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      from {&lt;br /&gt;
        namespace_selector {&lt;br /&gt;
          match_labels = {&lt;br /&gt;
            allow_ldap = true&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    # This defines the pod that this network policy will be applied to.&lt;br /&gt;
    pod_selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = &amp;quot;affected-pod&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    # We have defined both Ingress and Egress rules for this pod&lt;br /&gt;
    policy_types = [&amp;quot;Ingress&amp;quot;, &amp;quot;Egress&amp;quot;]&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This shows an example configuration, allows the defined pod to communicate with a database (note that the database will also be required to have a network policy with an ingress rule allowing the pod to connect) and DNS and allow other pods in the cluster to communciate with it. Note that any http port is missing as traffic is automatically permitted to access any port within the cluster.&lt;br /&gt;
&lt;br /&gt;
=== Helm ===&lt;br /&gt;
[https://helm.sh/ Helm] is a tool which allows the deployment of a set of kubernetes resources from a single configuration. So for third party, complicated applications its amazing, and there are a lot hosted on [https://artifacthub.io/ artifacthub]  and other random places (as you can really easily host a helm repo for free). There are benefits, like being able to rollback a change to a previous version. However, there are a few things to note:&lt;br /&gt;
&lt;br /&gt;
* When configuring in terraform, do NOT add repos! The full url should go in the &amp;lt;code&amp;gt;repo&amp;lt;/code&amp;gt; config, or if its an oci url, it should go in the &amp;lt;code&amp;gt;chart&amp;lt;/code&amp;gt; attribute.&lt;br /&gt;
* Tofu will refuse to deploy a deploying helm chart (you have to rollback first)&lt;br /&gt;
* &#039;&#039;&#039;DO NOT STORE SECRETS IN VALUES&#039;&#039;&#039; - values are not stored securely, and so you should never store passwords or api keys directly in the values (this is an easy mistake to make when configuring), all previous sets of values will be stored forever in the cluster. Therefore, if you make this mistake, you will have to rotate the secret or delete the whole helm deployment and start again.&lt;br /&gt;
* Helm does not care if resources are changed between deployments - this is both good and bad, it means that you can apply &amp;quot;hacks&amp;quot; to helm charts you know will not change and they will not appear in the terraform plans to be fixed. But again this is not particularly good practice and can result in some confusing behaviour.&lt;br /&gt;
* When you delete a deployment, all persistant volumes will get wiped unless they have the &amp;quot;Retain&amp;quot; reclaim policy.&lt;br /&gt;
&lt;br /&gt;
Helm is a great tool for quickly deploying whole clusters of applications, but it should be used with caution, making sure the chart is reputable and well maintained. As we are using terraform for deployments, it should also only be used for third party applications.&lt;br /&gt;
&lt;br /&gt;
=== Persistant Volumes ===&lt;br /&gt;
[https://kubernetes.io/docs/concepts/storage/persistent-volumes/ Persistant volumes] (PVs) and persistant volume claims (PVCs) are one of the more convoluted things in kubernetes and one of the more dangerous as you are handling data.&lt;br /&gt;
&lt;br /&gt;
At a high level, you request storage by creating a persistant volume claim, the storage manager fullfills your claim by creating a persistant volume (which does not have an associated namespace) and then it is assigned to your pod. You should never be creating persistant volumes yourself, and probably want to be creating persistant volumes through the statefulset&#039;s template interface.&lt;br /&gt;
&lt;br /&gt;
For us, the storage operator is k3s&#039; built in one, but for clusters with multiple pods, it probably is going to be something like [https://longhorn.io/ longhorn], which manages keeping replicas of the storage on multiple machines. This brings up a core issue, a persistant volume can only be mounted to a pod on a node that stores the persistant volume, which is why longhorn is necessary on multi-node clusters and is why we decided to only have one node in each of our clusters.&lt;br /&gt;
&lt;br /&gt;
PVs have different types of [https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes access modes]: &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ReadOnlyMany&amp;lt;/code&amp;gt; which mostly control how many nodes can read or write to it at once (multiple pods can still mount it, as long as they are all on the same node). So for k3s&#039; storage class does not support &amp;lt;code&amp;gt;ReadWriteMany&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;ReadOnlyMany&amp;lt;/code&amp;gt;, so you should only be setting it to &amp;lt;code&amp;gt;ReadWriteOnce&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Reclaiming PVs is one of the big danger factors. By default a persistant volume will be deleted if there are no longer any claims for it, and the claims will probably get deleted (e.g. in helm and statefulsets). Therefore, if you have important data on a pod, you most likely want to set the [https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaim-policy reclaim policy], that means the volume cannot be automatically deleted, but it also means, if another PVC comes along and matches the PV, the PV will be assigned to the new claim and subsequently a new pod, which can cause a lot of confusion and headache. But it is more likely that the PVC is the redeployment of the original application and so you want it to be reassigned to the new PVC.&lt;br /&gt;
{{Note|text=If you delete an application, you MUST delete the PV manually when its reclaim policy is &amp;quot;Retain&amp;quot;, otherwise the data will never get deleted and could be reassigned to another claim.|type=warn}}&lt;br /&gt;
&lt;br /&gt;
===== Other volumes =====&lt;br /&gt;
Persistant volumes are probably the easiest to understand, but there are a lot of other types of volumes, most notably empty directorys and ephemoral volumes as well as volumes creating from config maps or secrets. All of these serve different purposes:&lt;br /&gt;
&lt;br /&gt;
* Empty directory - something like &amp;lt;code&amp;gt;/tmp&amp;lt;/code&amp;gt;, all data will be stored in RAM if set to writable and will be deleted when the pod restarts or is deleted. You should set a limit to how much RAM is allowed to be stored there, as it could soak up the full resources of the computer.&lt;br /&gt;
* [https://kubernetes.io/docs/concepts/storage/ephemeral-volumes/ Emphemeral volumes] - similar to empty directorys, but instead of being stored in RAM, they are stored on disk (through persistant volumes) and will be deleted upon deletion of the pod and so mostly not really useful unless you are expecting to generate a lot of data during the running of the pod and don&#039;t want to retain them on reboot.&lt;br /&gt;
* Mounting secrets - this is read only storage and just allows you to mount configuration files that store database passwords or similar. Each key within the secret becomes a text file. Note that if its a binary file (e.g. image) you can use the &amp;lt;code&amp;gt;binaryData&amp;lt;/code&amp;gt; property and pass in a hex string.&lt;br /&gt;
* Mounting config maps - similar to secrets, but just are for files that don&#039;t have to be secret and encrypted on the machine.&lt;br /&gt;
&lt;br /&gt;
=== Cronjobs ===&lt;br /&gt;
&lt;br /&gt;
=== K3S ===&lt;br /&gt;
&lt;br /&gt;
== Deployment with Tofu ==&lt;br /&gt;
Information pertaining to terraform configuration itself can be found on [[Terraform|the Terraform page]] and for specific how to deploy to BOSS&#039; production and staging cluster, please see the [https://gitlab.bath.ac.uk/cs/int/terraform project&#039;s README] and [https://gitlab.bath.ac.uk/cs/boss/int-wiki internal wiki] for more information. This will mostly focus on the general process of deploying with tofu and the struggles.&lt;br /&gt;
&lt;br /&gt;
[https://k9scli.io/ k9s] is recommended for watching deployments and seeing why they failed as its generally just fantastic the more you get used to it. The common shortcuts you need to know are:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;d&amp;lt;/code&amp;gt; will describe the currently selected object. If you go to the bottom (&amp;lt;code&amp;gt;Shift-G&amp;lt;/code&amp;gt;) you will be able to see events associated with that resource, for pods and statefulsets this is &#039;&#039;&#039;extremely useful&#039;&#039;&#039; as it includes failures for pulling images or security denials.&lt;br /&gt;
* &amp;lt;code&amp;gt;l&amp;lt;/code&amp;gt; see the logs of all containers associated with the resource.&lt;br /&gt;
* &amp;lt;code&amp;gt;x&amp;lt;/code&amp;gt; while within a secret/configmap will show you the raw (string) data&lt;br /&gt;
* &amp;lt;code&amp;gt;s&amp;lt;/code&amp;gt; to shell into a pod (note that some pods do not support this as they do not ship withsh&lt;br /&gt;
* &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; allows you to perform a rolling restart on a statefulset or deployment (meaning there should be no downtime if everything is configured). This is the recommended way to update a pod on production (&#039;&#039;&#039;do not&#039;&#039;&#039; just delete the pod itself).&lt;br /&gt;
* &amp;lt;code&amp;gt;&amp;lt;enter&amp;gt;&amp;lt;/code&amp;gt; see any subresources (e.g. containers for pods, or pods for deployments/statefulsets)&lt;br /&gt;
* &amp;lt;code&amp;gt;Ctrl-d&amp;lt;/code&amp;gt; allows you to delete the resource&lt;br /&gt;
* &amp;lt;code&amp;gt;Ctrl-f&amp;lt;/code&amp;gt; allows you to add a port-forward to your own machine (really useful for debugging if an application is responding but is not available via traefik), or getting direct access to databases.&lt;br /&gt;
* &amp;lt;code&amp;gt;:&amp;lt;resource&amp;gt; &amp;lt;namespace|all&amp;gt;&amp;lt;/code&amp;gt; will change the list view to the given resource in the namespace&lt;br /&gt;
* &amp;lt;code&amp;gt;:&amp;lt;resource&amp;gt;&amp;lt;/code&amp;gt; will change the list view for the given resource in the currently selected namespace&lt;br /&gt;
* &amp;lt;code&amp;gt;0-9&amp;lt;/code&amp;gt; are shortcuts to switch between recently selected namespaces (they should show at the top with the current assignment)&lt;br /&gt;
&lt;br /&gt;
Some resources may also have their own special command set to allow you to perform resource specific actions which is nice (these should be listed at the top right).&lt;br /&gt;
&lt;br /&gt;
Back to deployment, it&#039;s mostly:&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu apply -var-file=./prod.tfvars [-target=module.something]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Using &amp;lt;code&amp;gt;-target&amp;lt;/code&amp;gt; can help a lot when the terraform module is huge as it will limit the number of resources it has to check for updates (though this means that you state and configuration can become desynced and so you should still do apply&#039;s without the targetting). You can even specify the exact resource you have just edited for super fast (for terraform) iterations.&lt;br /&gt;
&lt;br /&gt;
While it&#039;s deploying you cannot hit &amp;lt;code&amp;gt;Ctrl-c&amp;lt;/code&amp;gt; or well you can (twice) to force exit the application, but this can result in annoying consequencies:&lt;br /&gt;
&lt;br /&gt;
* Hitting it before you&#039;ve confirmed the plan (e.g. you accidentally forgot to include target and don&#039;t want to wait): The state &#039;&#039;&#039;WILL&#039;&#039;&#039; be locked and will not be unlocked. Therefore you have to run&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
tofu force-unlock &amp;lt;uid&amp;gt;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Where the &amp;lt;code&amp;gt;&amp;lt;uid&amp;gt;&amp;lt;/code&amp;gt; can be found by trying to run the apply command and it failing.&lt;br /&gt;
&lt;br /&gt;
* Hitting it while it&#039;s deploying new resources (e.g. the pods are not deploying and you didn&#039;t change the 5 minute timeout): on next run, tofu will try to recreate those resources, and so before running the command, you must open k9s, find the resources and delete them.&lt;br /&gt;
* Hitting it while updating helm charts (e.g. you didn&#039;t change the 10 minute timeout and it&#039;s not working): on the next run, tofu will refuse to deploy it, because the helm deployment is in an invalid state (&amp;quot;deploying&amp;quot;) and it will never exit this state. Therefore you must open up &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; type &amp;lt;code&amp;gt;helm &amp;lt;namespace&amp;gt;&amp;lt;/code&amp;gt;, wait a year, click &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; on the effected resource to list all the previous releases, go down to the last successful deployment and click &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt; again to rollback to that release, and again wait a year while k9s refuses to respond.&lt;br /&gt;
&lt;br /&gt;
As you can probably tell deployment takes patience, expecially when you don&#039;t alter the timeouts in helm (which default to 10 minutes for creation and 10 minutes for deletion). You don&#039;t want to interrupt the flow while tofu is doing it&#039;s thing, so make sure you aren&#039;t going to have to wait 10 minutes because you made a simple typo causing the pods to crash loop.&lt;br /&gt;
&lt;br /&gt;
You can technically, temporarily edit the resources (with &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; in k9s) to fix your mistake and to make it successfully deploy, so you can actually fix the mistake and redeploy (updating all the resources again) within a shorter time than it takes for tofu to timeout. You do want to be careful with timeouts however, as if you are on poor wifi (e.g. a train) or if the cluster is a bit pinned atm, deployments will take longer and the worst thing is if tofu times out but the deployment actually succeeded (though just reapply should update the state without an actualy redeploymenht unless its helm).&lt;br /&gt;
&lt;br /&gt;
=== Retaining PVs ===&lt;br /&gt;
As previously mentioned, whenever deploying new applications, you should mark any persistant volume that stores data you do not want accidentally deleted with a &amp;quot;Retain&amp;quot; reclaim policy. This can easily be done using &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; by typing &amp;lt;code&amp;gt;:pv&amp;lt;/code&amp;gt;, finding the pv attached to the claim, pressing &amp;lt;code&amp;gt;e&amp;lt;/code&amp;gt; and searching for where the &amp;lt;code&amp;gt;reclaimPolicy&amp;lt;/code&amp;gt; is defined. By default it will be &amp;quot;Delete&amp;quot;, and you can just replace it with &amp;quot;Retain&amp;quot;. &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; will also show you a PV&#039;s reclaim policy on the list view which is nice to check if its already been done.&lt;br /&gt;
&lt;br /&gt;
=== Where is the state stored? ===&lt;br /&gt;
{{Note|text=State files store all passwords and secrets generated or collected by terraform unencrypted by default and so you should be extremely cautious with where the files are stored, who has access to them and never commit them.|type=warn}}&lt;br /&gt;
Usually, if you don&#039;t define any backends, it is stored in a &amp;lt;code&amp;gt;.tfstate&amp;lt;/code&amp;gt; file within the directory of your folder. If you are just managing your cluster with them, an easy place to store it is within the kubernetes cluster itself, using the kubernetes backend:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
terraform {&lt;br /&gt;
  # ...&lt;br /&gt;
&lt;br /&gt;
  backend &amp;quot;kubernetes&amp;quot; {&lt;br /&gt;
    secret_suffix  = &amp;quot;my-cluster&amp;quot;&lt;br /&gt;
    config_paths   = [var.kube_config_path]&lt;br /&gt;
    config_context = var.kube_context&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  # ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This is BOSS&#039;s method of storage as we do not have to trust the GitLab instance with our cluster&#039;s life. But others can be chosen, for example [https://docs.gitlab.com/user/infrastructure/iac/terraform_state/ within GitLab itself], although this should be done with extreme caution due to the risk of exposing plan files (which contain secrets) to the world.&lt;br /&gt;
&lt;br /&gt;
=== Managing the state ===&lt;br /&gt;
&lt;br /&gt;
== General Terraform management ==&lt;br /&gt;
&lt;br /&gt;
== K9s ==&lt;br /&gt;
&lt;br /&gt;
== Migrating storages ==&lt;br /&gt;
&lt;br /&gt;
== Multicluster setups ==&lt;br /&gt;
&lt;br /&gt;
=== Control plane ===&lt;br /&gt;
&lt;br /&gt;
=== How to manage Storage ===&lt;br /&gt;
&lt;br /&gt;
=== Safely shutting down nodes ===&lt;br /&gt;
&lt;br /&gt;
== Backups ==&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Kubernetes event log ===&lt;br /&gt;
&lt;br /&gt;
=== Traefik dashboard ===&lt;br /&gt;
&lt;br /&gt;
=== Shell-ing into pods ===&lt;br /&gt;
&lt;br /&gt;
=== K3S Log ===&lt;br /&gt;
&lt;br /&gt;
=== Emergency Debug pods ===&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare API tokens ===&lt;br /&gt;
&lt;br /&gt;
=== SELinux ===&lt;br /&gt;
&lt;br /&gt;
=== Tofu ===&lt;br /&gt;
&lt;br /&gt;
=== Scenarios ===&lt;br /&gt;
&lt;br /&gt;
==== Deployment/statefulset created but pod not creating ====&lt;br /&gt;
&lt;br /&gt;
==== Pod in cash loop ====&lt;br /&gt;
&lt;br /&gt;
==== Pod cannot request any website ====&lt;br /&gt;
&lt;br /&gt;
==== Pod can access the internet but everything returns self-signed certificate ====&lt;br /&gt;
&lt;br /&gt;
==== Deployment can&#039;t access its database/valkey ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is requesting denied system privileges ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 404 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 403 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Authelia refuses to start up ====&lt;br /&gt;
&lt;br /&gt;
==== Node is crashing often after running out of RAM ====&lt;br /&gt;
&lt;br /&gt;
==== On IPV6 cluster and requests randomly timeout or return 404 ====&lt;br /&gt;
&lt;br /&gt;
==== Statefulset refusing to start pod (PVC) ====&lt;br /&gt;
&lt;br /&gt;
==== A node just crashed and went offline ====&lt;br /&gt;
&lt;br /&gt;
=== Cleaning up ===&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/Advanced&amp;diff=143</id>
		<title>Git/Advanced</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/Advanced&amp;diff=143"/>
		<updated>2026-06-05T13:56:41Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* See also */ Include Git in pages&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Git is a really powerful command line tool when you start looking into the advanced things you can do, but when maintaining or developing a project, you are probably not going to touch most of them. This is a list of ones which I use on a regular basis.&lt;br /&gt;
&lt;br /&gt;
It’s important to note that the more advanced commands can have quite powerful functions which can lose code if used incorrectly. However, in most cases git caches a significant amount, so if you lose a commit or some changes, &#039;&#039;&#039;don’t panic&#039;&#039;&#039; and look up your issue (someone has done this before and found a way to get the changes back).&lt;br /&gt;
&lt;br /&gt;
== More on Commit IDs ==&lt;br /&gt;
&lt;br /&gt;
Where it says to use the &amp;lt;code&amp;gt;commit-id&amp;lt;/code&amp;gt;, there are different inputs you can use to specify multiple commits easily.&lt;br /&gt;
&lt;br /&gt;
Let A and B be the ids for two different commits. To reference a range you can do:&lt;br /&gt;
&lt;br /&gt;
* A to B (including A): &amp;lt;code&amp;gt;A^..B&amp;lt;/code&amp;gt;&lt;br /&gt;
* A to B (excluding A): &amp;lt;code&amp;gt;A..B&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can specify the last &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; commits:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;HEAD~X&amp;lt;/code&amp;gt; e.g. &amp;lt;code&amp;gt;git reset --soft HEAD~4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Stashing ==&lt;br /&gt;
&lt;br /&gt;
Stashing is used when you want to record the current state of the working directory and the index, but want to go back to a clean working directory.&lt;br /&gt;
&lt;br /&gt;
It allows you to set aside some changes for later or just never commit them (without altering the [[Git/Special files#Gitignore|&amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file]]).&lt;br /&gt;
&lt;br /&gt;
By stashing your changes, they will be removed so you can no longer see them, but you can always &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;apply&amp;lt;/code&amp;gt; the stash to get the back at any point.&lt;br /&gt;
&lt;br /&gt;
=== Commands ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git stash       # Stash current unstaged (but tracked) changes&lt;br /&gt;
git stash apply # Restore the last stash without deleting it&lt;br /&gt;
git stash pop   # Restore the last stash and delete it&lt;br /&gt;
git stash list  # List the stashed changes&lt;br /&gt;
git stash show  # Inspect the stashed changes&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|type=reminder|text=&lt;br /&gt;
GUI tools e.g. lazygit or VSCode (with git extension pack) normally have a better interface for adding and removing stashes - so probably use that instead.&lt;br /&gt;
}}This is usually used with &amp;lt;code&amp;gt;git switch&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;git checkout&amp;lt;/code&amp;gt; to allow you to switch branches when you still have uncommitted changes that may conflict.&lt;br /&gt;
&lt;br /&gt;
== Reverting vs Resetting ==&lt;br /&gt;
&lt;br /&gt;
To either reset or revert, you need the git commit hash id (or the start of it), this can be done via &amp;lt;code&amp;gt;git log&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Once you have that, the interface is quite similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git reset commit-id&lt;br /&gt;
git revert commit-id&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
They both do the same thing of reverting the changes from the given commit. But the difference is, &amp;lt;code&amp;gt;revert&amp;lt;/code&amp;gt; will create a new commit (therefore you can just run &amp;lt;code&amp;gt;git push&amp;lt;/code&amp;gt; or merging without any issues), whereas &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; will remove those commits outright.&lt;br /&gt;
&lt;br /&gt;
When resetting, the tree (simply, the list of commits) itself is altered. This means that when pushing to a repo which already contains that commit, you have to use &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt;. This will cause anyone else working on the same branch to lose their changes, as they are forced to run &amp;lt;code&amp;gt;git pull --force&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This command is dangerous so make sure you check everything before using it as it forces the upstream to be exactly like your local branch. So if you accidentally removed the wrong commit, you cannot easily get it back.&lt;br /&gt;
&lt;br /&gt;
=== Extra options ===&lt;br /&gt;
&lt;br /&gt;
With &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; you also have extra options which you may find useful. You can either use &amp;lt;code&amp;gt;--soft&amp;lt;/code&amp;gt; (Put all the changes of the commit in staged) or &amp;lt;code&amp;gt;--hard&amp;lt;/code&amp;gt; (which is the default, just forget all changes).&lt;br /&gt;
&lt;br /&gt;
With &amp;lt;code&amp;gt;revert&amp;lt;/code&amp;gt; you can use &amp;lt;code&amp;gt;--no-commit&amp;lt;/code&amp;gt; which will put the inverse of the changes in staged (and not create a new commit). This allows you to add multiple reverts or more changes in it.&lt;br /&gt;
&lt;br /&gt;
== Rebasing ==&lt;br /&gt;
&lt;br /&gt;
Rebasing is used when commits have been added to &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; and you want to bring them to your branch (with was branched off of &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
To rebase off of main (when you are in your branch), you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase main&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|text=&lt;br /&gt;
As we are changing the structure of the commit tree, you must then run &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt; once you are 100% happy none of your changes have been lost.&lt;br /&gt;
&lt;br /&gt;
As usual, if there is anyone else using your current branch, you shouldn’t do this.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
The steps it is takes are:&lt;br /&gt;
&lt;br /&gt;
* Temporarily reset all your commits which you added to this branch&lt;br /&gt;
* Apply all the new commits in &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; (or the given branch)&lt;br /&gt;
* Re-apply all of your commits&lt;br /&gt;
&lt;br /&gt;
=== Dealing with Conflicts ===&lt;br /&gt;
&lt;br /&gt;
Sometimes there may be conflicting changes from the changes added to main (e.g. you’ve changed the same line as another change).&lt;br /&gt;
&lt;br /&gt;
This is when it gets quite confusing and dangerous.&lt;br /&gt;
&lt;br /&gt;
If this happens, git will print out an error, saying what files are are in conflict and where to find them.&lt;br /&gt;
&lt;br /&gt;
If you run &amp;lt;code&amp;gt;git status&amp;lt;/code&amp;gt; you will see that some files are staged and some are not. The idea for these are:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Staged files&#039;&#039;&#039;: These are the files which are not conflicting and will be committed on &amp;lt;code&amp;gt;git rebase --continue&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Unstaged files&#039;&#039;&#039;: These are the files with conflicts&lt;br /&gt;
&lt;br /&gt;
If you open one of the conflicting files you will find that git has altered it where the conflicts are.&lt;br /&gt;
&lt;br /&gt;
The format is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt; HEAD&lt;br /&gt;
- Auto writing README&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- who&#039;s the above guy?&lt;br /&gt;
=======&lt;br /&gt;
- something&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- other&lt;br /&gt;
&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt; 8f309e1 (Test commit)&amp;lt;/pre&amp;gt;&lt;br /&gt;
{{Note|text= class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
VSCode has its own custom interface for this [https://code.visualstudio.com/docs/sourcecontrol/overview#_3way-merge-editor read more here]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
You will see the changes you made are below the &amp;lt;code&amp;gt;=======&amp;lt;/code&amp;gt; and the (updated) upstream code in &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is above it.&lt;br /&gt;
&lt;br /&gt;
What you have to do is, for each of these conflicts, to choose which one to keep (or create a mixture). To do this, you just remove everything that shouldn’t be there (which includes &amp;lt;code&amp;gt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt; HEAD&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;=======&amp;lt;/code&amp;gt;, etc). For example, in this case it should result in:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;- Auto writing README&lt;br /&gt;
- something&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- other&lt;br /&gt;
- who&#039;s the above guy?&amp;lt;/pre&amp;gt;&lt;br /&gt;
Once you are happy an entire file is now conflict-free and correct, you can stage it.&lt;br /&gt;
&lt;br /&gt;
Then once there are no unstaged files left you can run &amp;lt;code&amp;gt;git rebase --continue&amp;lt;/code&amp;gt;, which will save the changes under the original commit (sometimes it will ask you to confirm the commit message with your editor, you can just save and exit it).&lt;br /&gt;
&lt;br /&gt;
Once you have finished rebasing, make sure to test that your code still works. It’s common for code to break after rebasing due to unexpected changes made by someone else.&lt;br /&gt;
&lt;br /&gt;
=== Interactive Rebase ===&lt;br /&gt;
&lt;br /&gt;
Interactive rebasing is one of the most powerful and fun commands ever. However it comes with the downside of it being quite dangerous.&lt;br /&gt;
&lt;br /&gt;
It is used for reorganising a merge request and managing commits to clean up the git log.&lt;br /&gt;
&lt;br /&gt;
To use it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase -i HEAD~X&lt;br /&gt;
# Or interactive rebase off of main&lt;br /&gt;
git rebase -i main&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This should bring up your default text editor with a list of commits with a list of commands at the end, which looks like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;pick af44004 Annoying rebasing commit&lt;br /&gt;
pick ceb4454 Add quote from Varnie&lt;br /&gt;
pick 1f156e8 Test commit&lt;br /&gt;
&lt;br /&gt;
# Rebase 315c076..1f156e8 onto 315c076 (3 commands)&lt;br /&gt;
#&lt;br /&gt;
# Commands:&lt;br /&gt;
# p, pick &amp;amp;lt;commit&amp;amp;gt; = use commit&lt;br /&gt;
# r, reword &amp;amp;lt;commit&amp;amp;gt; = use commit, but edit the commit message&lt;br /&gt;
# e, edit &amp;amp;lt;commit&amp;amp;gt; = use commit, but stop for amending&lt;br /&gt;
# s, squash &amp;amp;lt;commit&amp;amp;gt; = use commit, but meld into previous commit&lt;br /&gt;
# f, fixup [-C | -c] &amp;amp;lt;commit&amp;amp;gt; = like &amp;amp;quot;squash&amp;amp;quot; but keep only the previous&lt;br /&gt;
#                    commit&#039;s log message, unless -C is used, in which case&lt;br /&gt;
#                    keep only this commit&#039;s message; -c is same as -C but&lt;br /&gt;
#                    opens the editor&lt;br /&gt;
# x, exec &amp;amp;lt;command&amp;amp;gt; = run command (the rest of the line) using shell&lt;br /&gt;
# b, break = stop here (continue rebase later with &#039;git rebase --continue&#039;)&lt;br /&gt;
# d, drop &amp;amp;lt;commit&amp;amp;gt; = remove commit&lt;br /&gt;
# l, label &amp;amp;lt;label&amp;amp;gt; = label current HEAD with a name&lt;br /&gt;
# t, reset &amp;amp;lt;label&amp;amp;gt; = reset HEAD to a label&lt;br /&gt;
# m, merge [-C &amp;amp;lt;commit&amp;amp;gt; | -c &amp;amp;lt;commit&amp;amp;gt;] &amp;amp;lt;label&amp;amp;gt; [# &amp;amp;lt;oneline&amp;amp;gt;]&lt;br /&gt;
#         create a merge commit using the original merge commit&#039;s&lt;br /&gt;
#         message (or the oneline, if no original merge commit was&lt;br /&gt;
#         specified); use -c &amp;amp;lt;commit&amp;amp;gt; to reword the commit message&lt;br /&gt;
# u, update-ref &amp;amp;lt;ref&amp;amp;gt; = track a placeholder for the &amp;amp;lt;ref&amp;amp;gt; to be updated&lt;br /&gt;
#                       to this position in the new commits. The &amp;amp;lt;ref&amp;amp;gt; is&lt;br /&gt;
#                       updated at the end of the rebase&lt;br /&gt;
#&lt;br /&gt;
# These lines can be re-ordered; they are executed from top to bottom.&lt;br /&gt;
#&lt;br /&gt;
# If you remove a line here THAT COMMIT WILL BE LOST.&lt;br /&gt;
#&lt;br /&gt;
# However, if you remove everything, the rebase will be aborted.&lt;br /&gt;
#&amp;lt;/pre&amp;gt;&lt;br /&gt;
You can see all the commits which will be re-applied after the branch has been rolled back and commits from main been added.&lt;br /&gt;
&lt;br /&gt;
These all have the word &amp;lt;code&amp;gt;pick&amp;lt;/code&amp;gt; infront of them, meaning they will be committed as is, with the same message, and no editing happening.&lt;br /&gt;
&lt;br /&gt;
You can look over the other commands to see what you can do, but how it works is by replacing &amp;lt;code&amp;gt;pick&amp;lt;/code&amp;gt; with something else, e.g. &amp;lt;code&amp;gt;fixup&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;reword&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== The Fixup Commit ===&lt;br /&gt;
&lt;br /&gt;
This is a weird option when committing, that can be useful when you have an MR and you are wanting to keep a neat commit log, but are responding to review feedback.&lt;br /&gt;
&lt;br /&gt;
How this works, is lets say you’ve fixed something with a previous commit in your MR and were wanting to (when merging) squash this fix into that commit. But you want to have it as a separate commit to help the reviewer to see that you have fixed it.&lt;br /&gt;
&lt;br /&gt;
To do this you simply run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git commit --fixup=&amp;quot;amend:&amp;lt;git_commit_id&amp;gt;&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which will create a new commit with the message:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;amend! Add information for publicising events from BCSS and where to go&lt;br /&gt;
&lt;br /&gt;
&amp;amp;gt; Add information for publicising events from BCSS and where to go&amp;lt;/pre&amp;gt;&lt;br /&gt;
Which you can then push as its own commit.&lt;br /&gt;
&lt;br /&gt;
Then when you come to merge you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase -i main --autosquash&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which will automatically move your amend commit to be &amp;lt;code&amp;gt;fixup&amp;lt;/code&amp;gt; above the commit you were amending.&lt;br /&gt;
&lt;br /&gt;
== Merging ==&lt;br /&gt;
&lt;br /&gt;
Normally merging is done via the interface on the remote repository system you are using. However it can be done in the command line as well (it can also be used in place of &amp;lt;code&amp;gt;rebase&amp;lt;/code&amp;gt; so you don’t have to use the dangerous &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
It copies all the commits which have been added on a branch to the branch you are currently on (e.g. &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
So the process to merge a branch into main is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git checkout main&lt;br /&gt;
git merge branch-name&lt;br /&gt;
git branch -d branch-name # Deletes the branch If you don&#039;t need the branch any&lt;br /&gt;
                          # more&lt;br /&gt;
git push&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Conflicts can still happen, see [[Git/Advanced#dealing-with-conflicts|above]] for more information about how to manage them.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
When merging, cherry-picking or other, and you are dealing with conflicts you need to use the subcommand with &amp;lt;code&amp;gt;--continue&amp;lt;/code&amp;gt;, for example &amp;lt;code&amp;gt;git merge --continue&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;git cherry-pick --continue&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Cherry-picking ==&lt;br /&gt;
&lt;br /&gt;
Cherry picking allows you to bring a single commit (or multiple, see [[#more-on-commit-ids|here]]) from another branch to your current one.&lt;br /&gt;
&lt;br /&gt;
To do this, it is as simple as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git cherry-pick commit-id&lt;br /&gt;
git push&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Submodules ==&lt;br /&gt;
&lt;br /&gt;
Submodules are normally used in what is called a “monorepo”, a repo which stores multiple difference projects or git repositories.&lt;br /&gt;
&lt;br /&gt;
It is also useful for refactoring some files. E.g. if you need the same files in multiple different projects (e.g. standardised tests or config files), it is common to add a “meta” repo which stores these projects, then add this as a submodule to each project which uses it.&lt;br /&gt;
&lt;br /&gt;
To add a submodule you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git submodule add repo-url folder/to/store&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This will clone the module inside the folder &amp;lt;code&amp;gt;folder/to/store&amp;lt;/code&amp;gt; and will add a &amp;lt;code&amp;gt;.gitmodule&amp;lt;/code&amp;gt; file in the base of the repo.&lt;br /&gt;
&lt;br /&gt;
When cloning the repo on other devices, you must remember to recursively clone all the submodules as well via:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git clone --recurse-submodules -j8 project-url&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
&amp;lt;code&amp;gt;-j8&amp;lt;/code&amp;gt; is an performance optimisation see [https://stackoverflow.com/questions/3796927/how-do-i-git-clone-a-repo-including-its-submodules#4438292 this Stack Overflow answer]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Or if you have already cloned the repo and were wanting to update all the submodules, you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git submodule update --init --recursive&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Managing submodules ===&lt;br /&gt;
&lt;br /&gt;
Once you have cloned a submodule, you will note that any time you pull the latest changes to it, you need to make another commit in the base repo with the update.&lt;br /&gt;
&lt;br /&gt;
This is so that the submodules are locked on specific commits until you specifically say “yes this next commit is fine”.&lt;br /&gt;
&lt;br /&gt;
== Extra Configuration ==&lt;br /&gt;
&lt;br /&gt;
Sometimes there are configuration options that git will recommend when they become a problem, for example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git config --global pull.rebase true&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This means that when pulling from a branch which has new changes, it will rebase instead of merging the new commits.&lt;br /&gt;
&lt;br /&gt;
=== Commit Signing ===&lt;br /&gt;
&lt;br /&gt;
Commit signing is used to verify if you are who you say you are when committing (e.g. with your email address).&lt;br /&gt;
&lt;br /&gt;
I won’t go into much depth on this, instead just know it exists and is quite good practice to have but not necessary.&lt;br /&gt;
&lt;br /&gt;
You can read more [https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits here].&lt;br /&gt;
&lt;br /&gt;
Be aware that there are some consequences which come along with this:&lt;br /&gt;
&lt;br /&gt;
* If you have setup a strict mode with signing, you cannot commit if you loose access to your signing key&lt;br /&gt;
* When rebasing your changes, you have to resign all the commits (meaning you are the only one who can do it)&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git|namespace=0|hideredirects=1}}&lt;br /&gt;
[[Category:Tutorial]]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/Special_files&amp;diff=142</id>
		<title>Git/Special files</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/Special_files&amp;diff=142"/>
		<updated>2026-06-05T13:56:16Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* See also */ Don&amp;#039;t show redirects&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;In git there are some files which have some importance, either on your remote repository or just in general.&lt;br /&gt;
&lt;br /&gt;
== README ==&lt;br /&gt;
&lt;br /&gt;
File location: &amp;lt;code&amp;gt;README.md&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
When creating a repository on GitHub or GitLab, it will ask if you want to initialise it with a README. This is a file written in [https://confluence.atlassian.com/bitbucketserver/markdown-syntax-guide-776639995.html markdown], which just contains a description of what the project is.&lt;br /&gt;
&lt;br /&gt;
If you visit a project e.g. [https://gitlab.bath.ac.uk/cs/services/froom froom], you will see a description rendered below all the files. This is the README, which both GitLab and GitHub will render on the home directory of your project.&lt;br /&gt;
&lt;br /&gt;
== LICENSE ==&lt;br /&gt;
&lt;br /&gt;
File location: &amp;lt;code&amp;gt;LICENSE&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;LICENSE.txt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This stores how a project can be used and altered, if none is given it can be assumed that all rights are reserved. There are strict laws protecting these statuses and so when copying code from open sources projects, please make sure you understand how you can use that code and what the license permits&lt;br /&gt;
&lt;br /&gt;
For [[BOSS]] projects, we have our own [https://boss.bathcs.com/policies/licenses/ License policy] which states all our projects should use the [https://spdx.org/licenses/LLVM-exception.html &amp;lt;code&amp;gt;Apache-2.0 WITH LLVM-exception&amp;lt;/code&amp;gt;] license If you are contributing to BOSS, please read the [https://boss.bathcs.com/policies/licenses/|license policy] for the full list of requirements.&lt;br /&gt;
&lt;br /&gt;
== Gitignore ==&lt;br /&gt;
&lt;br /&gt;
File location: &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This file stores a list of files or folders which should never be included in your git commits.&lt;br /&gt;
&lt;br /&gt;
These will usually include caching files or output directories which should be generated by the developer on their local machine.&lt;br /&gt;
&lt;br /&gt;
An example for a project using &amp;lt;code&amp;gt;deno&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;yarn&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;npm&amp;lt;/code&amp;gt; is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;# build output&lt;br /&gt;
dist/&lt;br /&gt;
&lt;br /&gt;
node_modules&lt;br /&gt;
.yarn&lt;br /&gt;
!.yarn/releases&lt;br /&gt;
!.yarn/plugins&lt;br /&gt;
.pnp.*&lt;br /&gt;
&lt;br /&gt;
.vscode&lt;br /&gt;
!.vscode/extensions.json&lt;br /&gt;
!.vscode/launch.json&lt;br /&gt;
&lt;br /&gt;
# logs&lt;br /&gt;
npm-debug.log*&lt;br /&gt;
yarn-debug.log*&lt;br /&gt;
yarn-error.log*&lt;br /&gt;
pnpm-debug.log*&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
# environment variables&lt;br /&gt;
.env&lt;br /&gt;
.env.production&lt;br /&gt;
&lt;br /&gt;
# macOS-specific files&lt;br /&gt;
.DS_Store&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
But these will vary depending on the tools and language you are using in a given project.&lt;br /&gt;
&lt;br /&gt;
[https://github.com/github/gitignore Templates] are available online or you can use [https://gitignore.io generate a gitignore] specific to your development setup.&lt;br /&gt;
&lt;br /&gt;
== Gitmodules ==&lt;br /&gt;
&lt;br /&gt;
File location &amp;lt;code&amp;gt;.gitmodules&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In most cases, this is not required and so will not be in a repository, this is a special file which is normally autogenerated by the [[Git/Advanced#Submodules|&amp;lt;code&amp;gt;submodule&amp;lt;/code&amp;gt; command]] in git (and so probably shouldn’t be edited manually).&lt;br /&gt;
&lt;br /&gt;
But this stores all the locations and repos of the submodules you have added.&lt;br /&gt;
&lt;br /&gt;
== GitLab CI ==&lt;br /&gt;
&lt;br /&gt;
File location &amp;lt;code&amp;gt;.gitlab-ci.yml&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This file controls the pipelines on the project, [[Git/GitLab#CI Pipelines|see here for more information]].&lt;br /&gt;
&lt;br /&gt;
== MR/Issue Templates ==&lt;br /&gt;
&lt;br /&gt;
File location &amp;lt;code&amp;gt;.gitlab/merge_request_templates/*.md&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;.gitlab/issue_templates/*.md&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This is better explained [[Git/GitLab#MR Templates|here]].&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git|namespace=0|hideredirects=1}}&lt;br /&gt;
[[Category:Tutorial]]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/Mirrors&amp;diff=141</id>
		<title>Git/Mirrors</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/Mirrors&amp;diff=141"/>
		<updated>2026-06-05T13:56:00Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* See also */ Don&amp;#039;t show redirects&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Some projects could be a student’s heart and soul. We do not want to take this away from them, instead we just want to store a copy of the current version on bathcs.com.&lt;br /&gt;
&lt;br /&gt;
So the solution to this is to have a mirror repo.&lt;br /&gt;
&lt;br /&gt;
There are multiple ways to solve this:&lt;br /&gt;
&lt;br /&gt;
== GitLab Mirror ==&lt;br /&gt;
&lt;br /&gt;
In GitLab go to “Settings &amp;amp;gt; Repository &amp;amp;gt; Mirroring Repositories”, you can see there is an option to “push” to another repo.&lt;br /&gt;
&lt;br /&gt;
This means that if the student has their own GitLab instance where they store their own version, they can setup this pushing feature to push to a repo on the GitLab.&lt;br /&gt;
&lt;br /&gt;
However, due to security restrictions, this only works when the other server is connected to the University Intranet, so it is not feasible.&lt;br /&gt;
&lt;br /&gt;
== Just do it manually ==&lt;br /&gt;
&lt;br /&gt;
When doing it manually, you can just push to two separate repositories when making changes locally, by going:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git add remote bath git@gitlab.bath.ac.uk:cs/wiki&lt;br /&gt;
git push&lt;br /&gt;
git push bath&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If you were wanting to have a specialised branch which has some extra features, you can do something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git checkout branch-name&lt;br /&gt;
git push bath HEAD:main&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git|namespace=0|hideredirects=1}}&lt;br /&gt;
[[Category:Tutorial]]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/GitLab&amp;diff=140</id>
		<title>Git/GitLab</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/GitLab&amp;diff=140"/>
		<updated>2026-06-05T13:55:45Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* See also */ Don&amp;#039;t show redirects&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;We require that the websites hosted on bathcs.com should be stored in [https://gitlab.bath.ac.uk/cs the cs organisation on Bath’s GitLab instance]. Any project used by student-led initiatives can also be stored on here.&lt;br /&gt;
&lt;br /&gt;
GitLab was chosen because it has a lot more (useful) features than GitHub. However this does mean that it is aimed at people who has a basic knowledge of how to properly manage git projects.&lt;br /&gt;
&lt;br /&gt;
Therefore you will need to be able to use GitLab.&lt;br /&gt;
&lt;br /&gt;
{{Note|type=warn|text=&lt;br /&gt;
You cannot push or pull to the GitLab outside of the VPN, even though you can access the page.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&lt;br /&gt;
You might have set this up when handing over, if you haven’t, here are the instructions. Note you will have to ask an organisation manager to add you to the organisation if you are going to be editing any current projects.&lt;br /&gt;
&lt;br /&gt;
* Go to https://gitlab.bath.ac.uk&lt;br /&gt;
* Login with your University credentials&lt;br /&gt;
* Setup TOTP as your 2FA (one time codes). See [[2FA#TOTP|our 2FA section]] for more information about what this is and how to add one (you can skip to the section where it says “Once you see a QR code”)&lt;br /&gt;
&lt;br /&gt;
Once you have logged in, your account will be created and so can be added to organisations or tagged (using @username).&lt;br /&gt;
&lt;br /&gt;
=== SSH Keys ===&lt;br /&gt;
&lt;br /&gt;
If you want to commit or clone repos, you will also need to setup an SSH key:&lt;br /&gt;
&lt;br /&gt;
# Create an SSH key: see [[SSH keys|our documentation]] for information on how to set one up.&lt;br /&gt;
# Go to your preferences (click profile icon then click preferences).&lt;br /&gt;
# Click “SSH Keys”.&lt;br /&gt;
# Copy and paste the public key into the “Key” section.&lt;br /&gt;
# Give it a title.&lt;br /&gt;
# Change usage type to “Authentication only”.&lt;br /&gt;
# Click “Add key”.&lt;br /&gt;
&lt;br /&gt;
By default this will expire in a year, after which you will have to add another key in the same way.&lt;br /&gt;
&lt;br /&gt;
=== Extra Emails ===&lt;br /&gt;
&lt;br /&gt;
You may have already set up git with your personal email address or you have a git signing key with a specific email. You probably don’t want to edit the configuration on a per project basis.&lt;br /&gt;
&lt;br /&gt;
To solve this, GitLab and GitHub allow you to have multiple email addresses linked to your account, this can be found by going to preferences (click the profile icon then preferences) then clicking “Emails”.&lt;br /&gt;
&lt;br /&gt;
You can then type in another email into the box and click “Add email address”. You will then have to verify the email by clicking the link it gives you.&lt;br /&gt;
&lt;br /&gt;
== The CS Organisation ==&lt;br /&gt;
&lt;br /&gt;
All Student Led Initiative projects are stored on the [https://gitlab.bath.ac.uk/cs Computer Science SLI organisation]. If you are a committee member you should have been given access to it. If not please contact the current owners, who can be found by going “Group information &amp;amp;gt; Members”.&lt;br /&gt;
&lt;br /&gt;
[[File:git-gitlab_members.gif|Members page on GitLab showing the current owners and maintainers found by going to Members in the navigation group “Group Information”]]&lt;br /&gt;
&lt;br /&gt;
The organisation or group has subgroups for all societies or student-led initiatives. You should have developer rights on all initiatives which you are a part of, which allows you to create branches on the existing projects and push to them. If you ask nicely you may get maintainer rights, which will allow you to create new projects and merge MRs into main (however you still can’t push to the &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; branch).&lt;br /&gt;
&lt;br /&gt;
There are some projects which live outside the subgroups (e.g. this wiki) as they don’t have a organisation apart of. You will have to specifically ask permission to access individual projects for security reasons.&lt;br /&gt;
&lt;br /&gt;
== Issues ==&lt;br /&gt;
&lt;br /&gt;
=== Creating ===&lt;br /&gt;
&lt;br /&gt;
[[File:git-create_issue.gif|gif showing the steps of going to the issues menu under pinned and clicking new issue button]]&lt;br /&gt;
&lt;br /&gt;
Creating an issue is easy, you can click on the “Issues” tab under “pinned” and click “New Issue” and start typing. The description is written in markdown. You can [https://docs.gitlab.com/ee/user/markdown.html learn markdown on the GitLab website].&lt;br /&gt;
&lt;br /&gt;
If the project is configured with templates, there should be a dropdown for you to choose the one most relavent to your issue type.&lt;br /&gt;
&lt;br /&gt;
[[File:git-mr_templates.gif|gif showing the dropdown to select the template to use above the description]]&lt;br /&gt;
&lt;br /&gt;
You can then assign someone an issue from the menu if you know who will be working on it. Labels are also available, but these are mostly for the maintainers of the project to use.&lt;br /&gt;
&lt;br /&gt;
Once the issue is created, it will assign the issue a number (you can see this in the URL), which you can use to cross-reference issues or MRs by putting a &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; before it. GitLab will pick this up and turn it into a link (you can also just put the link and it will be sortened). Note that issues on other projects can also be referenced.&lt;br /&gt;
&lt;br /&gt;
=== Managing Issues ===&lt;br /&gt;
&lt;br /&gt;
There is not much to say about managing issues. All conversations about the issue should be done on the issue itself.&lt;br /&gt;
&lt;br /&gt;
Once someone is working on it, they should be assigned the issue and it should be referenced in the MR which fixes it.&lt;br /&gt;
&lt;br /&gt;
If “Closes” is put before the link to the issue on an MR, once it is merged, the issue should automatically be closed (but please always do check all the related issues are closed).&lt;br /&gt;
&lt;br /&gt;
== Forking + Branches ==&lt;br /&gt;
&lt;br /&gt;
If you are a developer or maintainer on a project, you should be able to create a branch on the project (but just not push directly main). Branches should be under the format &amp;lt;code&amp;gt;&amp;amp;lt;username&amp;amp;gt;/&amp;amp;lt;fix_summary&amp;amp;gt;&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
If you are not a developer but would like to contribute to a project, you should be able to fork the repository into your own space to create make any edits you wish.&lt;br /&gt;
&lt;br /&gt;
== Merge Requests ==&lt;br /&gt;
&lt;br /&gt;
=== Creating ===&lt;br /&gt;
&lt;br /&gt;
[[File:git-create_mr.gif|gif showing the steps of going to the merge requests menu under pinned and clicking new merge request button and choosing a branch to merge into main]]&lt;br /&gt;
&lt;br /&gt;
Once you have created a fork or branch, you can create a merge request, either by clicking the link which is shown after pushing, or going onto the project and clicking “Merge requests” at the left and choosing “New merge request”. You can then choose the branch you want to merge from and the one you are merging to (most likely &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
The interface is very similar to issues, with the summary written in markdown and templates available.&lt;br /&gt;
&lt;br /&gt;
If the Merge Request is not finished, and so still a work in progress, you can click the “Mark Draft” button or put “DRAFT:” at the start of the summary.&lt;br /&gt;
&lt;br /&gt;
You can then assign the MR to yourself (as you will be managing it). You then also want to set the reviewer to a maintainer who can then merge the issue.&lt;br /&gt;
&lt;br /&gt;
If the MR depends on another MR to be merged beforehand, please add the “blocked” label and mention what its blocked on in a comment or the issue directly.&lt;br /&gt;
&lt;br /&gt;
=== Managing ===&lt;br /&gt;
&lt;br /&gt;
Sadly as the university does not pay for premium GitLab, we have to do most security practices as manual steps. So please do not go against these procedures, we beg of you.&lt;br /&gt;
&lt;br /&gt;
If you are the reviewer of a MR, you can go onto the “Changes” tab where you can see all the differences between this and the main branch. There is a settings button where you can change parts of the look. I recommend enabling “show whitespace changes” to help find where people have accidentally left trailing whitespace.&lt;br /&gt;
&lt;br /&gt;
You can then add a comment on a line by hovering over the line and clicking the comment button on the left hand side. You can also do this on images if you wish.&lt;br /&gt;
&lt;br /&gt;
When adding a comment you have the option to “start a review” which is recommended if you are going to be making multiple comments as it send them out all at once instead of having a separate email for each comment (which is a lot).&lt;br /&gt;
&lt;br /&gt;
If you drag the comment, you can also select multiple lines which is useful for segments of code which need changing.&lt;br /&gt;
&lt;br /&gt;
[[File:git-start_review.gif|gif showing going to commits tab and adding a comment to the review, selecting multiple lines]]&lt;br /&gt;
&lt;br /&gt;
It is expected that the assignee will then resolve all of these issues so it is not up to the reviewer (but as we are students they may need chasing).&lt;br /&gt;
&lt;br /&gt;
Once you are happy with the MR, you can then approve the merge request before merging it.&lt;br /&gt;
&lt;br /&gt;
Please make sure that:&lt;br /&gt;
&lt;br /&gt;
* You don’t approve your own MR&lt;br /&gt;
* Don’t merge something without approval (if it is going into &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Don’t approve without actually reading all the changes&lt;br /&gt;
&lt;br /&gt;
== Creating a new Project ==&lt;br /&gt;
&lt;br /&gt;
Over time we may want to add more projects to the organisation. This page focuses on creating projects from scratch. If you are just wanting to mirror, see the [[Git/Mirrors|page about mirrors]].&lt;br /&gt;
&lt;br /&gt;
When creating a new repo or project, you need to decide which subgroup it should go under. This should be quite simple, however you may decide a new subgroup is required.&lt;br /&gt;
&lt;br /&gt;
If you are not a maintainer in a repository, you will have to find the maintainers of the subgroup and ask one of them to set up the repo for you and then add you to the access list.&lt;br /&gt;
&lt;br /&gt;
Other things to consider when creating a project:&lt;br /&gt;
&lt;br /&gt;
* Name (please make this understandable about what it is for)&lt;br /&gt;
* Visibility level:&lt;br /&gt;
** &#039;&#039;&#039;Private&#039;&#039;&#039;: only people with access can see it (preferably not), however is understandable if you are just testing out a project.&lt;br /&gt;
** &#039;&#039;&#039;Internal&#039;&#039;&#039;: Only bath students can see it when logged in, this is useful for things which interact with University of Bath services which they may not want known to the public.&lt;br /&gt;
** &#039;&#039;&#039;Public&#039;&#039;&#039;: This should be used for most projects to encourage the open source nature of the community.&lt;br /&gt;
&lt;br /&gt;
Once a repository is created you can add it as a remote to your repo or clone it.&lt;br /&gt;
&lt;br /&gt;
You should then:&lt;br /&gt;
&lt;br /&gt;
* Go to “Settings &amp;amp;gt; Repository &amp;amp;gt; Protected branches” and change “Allowed to push and merge” to be “No one” and make sure “Merge MRs” is set to “Maintainers only”.&lt;br /&gt;
* Add a description or logo in the “Settings &amp;amp;gt; General” tab&lt;br /&gt;
* Add a license as explained [[Git/Special files#license|here]].&lt;br /&gt;
&lt;br /&gt;
[[File:git-setup_new_project.gif|gif showing the process of editing the protected branches to only allow maintainers to merge to main and no one to push to the main]]&lt;br /&gt;
&lt;br /&gt;
=== Manage Access ===&lt;br /&gt;
&lt;br /&gt;
You can choose who can access the repository in “Manage &amp;amp;gt; Members” if you are a Maintainer or Owner.&lt;br /&gt;
&lt;br /&gt;
To add new person you can click “Invite members” and type in their username (they will need to have signed into GitLab for their username to appear).&lt;br /&gt;
&lt;br /&gt;
You then need to choose their role. It is recommended to put them as “Developer” unless you want them to be able to merge requests into &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;, then put them as “Maintainer”.&lt;br /&gt;
&lt;br /&gt;
=== Labels ===&lt;br /&gt;
&lt;br /&gt;
Under “Manage &amp;amp;gt; Labels” you can add or create labels which can be used on merge requests or issues. You should inherit any labels from the parent group, and preferably labels should be group wide. But you can create a label and ask an Owner to upgrade the label to a group label by clicking the 3 dots.&lt;br /&gt;
&lt;br /&gt;
=== Issue + MR Templates ===&lt;br /&gt;
&lt;br /&gt;
In your project, you may want to have templates for people creating issues or MRs to save them time and to standardise them.&lt;br /&gt;
&lt;br /&gt;
This is quite simple to do in GitLab as explained [https://docs.gitlab.com/ee/user/project/description_templates.html here].&lt;br /&gt;
&lt;br /&gt;
In short, you need to add markdown files to &amp;lt;code&amp;gt;.gitlab/issue_templates/&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;.gitlab/merge_request_templates/&amp;lt;/code&amp;gt;. The default should be named &amp;lt;code&amp;gt;default.md&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;.gitlab/issue_templates/default.md&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;### Summary&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!--Add brief summary explaining the issue--&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
### Steps to reproduce&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Add brief instructions to reproduce the issue --&amp;amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;.gitlab/merge_request_templates/default.md&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;### Related Issue&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!--Replace with the related issue number--&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
Closes #&lt;br /&gt;
&lt;br /&gt;
### Summary&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Summary of what the MR does --&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
### Testing&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Summary of the testing done to validate the MR --&amp;amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;.gitlab/merge_request_templates/bug_fix.md&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;### Summary&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Summary of what the the bug was --&amp;amp;gt;&lt;br /&gt;
&lt;br /&gt;
### Testing&lt;br /&gt;
&lt;br /&gt;
&amp;amp;lt;!-- Summary of the testing done to validate the MR --&amp;amp;gt;&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== CI Pipelines ===&lt;br /&gt;
&lt;br /&gt;
GitLab pipelines are super powerful and I don’t think I could write better documentation than GitLab themselves (which can be found [https://docs.gitlab.com/ee/ci/pipelines/ here]).&lt;br /&gt;
&lt;br /&gt;
This is just here to say they do exist and they should be used for automated testing or even deployment.&lt;br /&gt;
&lt;br /&gt;
The configuration can be found in the &amp;lt;code&amp;gt;.gitlab-ci.yml&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git|namespace=0|hideredirects=1}}&lt;br /&gt;
[[Category:Tutorial]]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/Basic_Components&amp;diff=139</id>
		<title>Git/Basic Components</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/Basic_Components&amp;diff=139"/>
		<updated>2026-06-05T13:55:04Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* See also */ Don&amp;#039;t show redirects&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Git is split up into multiple different subcommands. Here are a few essential ones to understand to use git.&lt;br /&gt;
&lt;br /&gt;
== Initialisation ==&lt;br /&gt;
&lt;br /&gt;
This is not entirely necessary, however it helps to understand what is happening when you clone a repository and the fact that a folder can be initialised with git without being reliant on a remote repository.&lt;br /&gt;
&lt;br /&gt;
When creating a new project locally, you can make the repository be version-controlled by running&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git init&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If you then run &amp;lt;code&amp;gt;git status&amp;lt;/code&amp;gt; you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;On branch main&lt;br /&gt;
nothing to commit, working tree clean&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_init.gif|gif showing what is explained in this section of initialising a git repo]]&lt;br /&gt;
&lt;br /&gt;
=== Adding a remote ===&lt;br /&gt;
&lt;br /&gt;
Adding a remote repository is not necessary unless you want to be pushing to one as some sort of backup or if you just want to share your code.&lt;br /&gt;
&lt;br /&gt;
To add one (you can also have multiple), you simply run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git remote add origin &amp;lt;url&amp;gt;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Here we are creating a remote called &amp;lt;code&amp;gt;origin&amp;lt;/code&amp;gt;, you can change this name if you have multiple. The default name is &amp;lt;code&amp;gt;origin&amp;lt;/code&amp;gt; and is what we will be using later on.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_remote_add.gif|gif showing the process of adding a remote repo and pulling from it]]&lt;br /&gt;
&lt;br /&gt;
== Cloning ==&lt;br /&gt;
&lt;br /&gt;
Cloning basically does everything we explained above automatically for you and then pulls the latest commits to your folder.&lt;br /&gt;
&lt;br /&gt;
The command to clone a repository is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git clone &amp;lt;url&amp;gt;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This clones the repo into the folder with the same name as the project. E.g. if I were to clone &amp;lt;code&amp;gt;git@gitlab.bath.ac.uk:cs/wiki&amp;lt;/code&amp;gt;, it would create a folder called &amp;lt;code&amp;gt;wiki&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
If you want a custom folder name, you just simply add another parameter e.g:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git clone git@gitlab.bath.ac.uk:cs/wiki bathcs_wiki&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_clone.gif|gif showing the process of cloning a repository]]&lt;br /&gt;
&lt;br /&gt;
== Staging and Committing ==&lt;br /&gt;
&lt;br /&gt;
Staging and committing are essential parts of git. The process goes:&lt;br /&gt;
&lt;br /&gt;
* You make some changes&lt;br /&gt;
* You stage those changes (basically saying “yes I want these changes to be in the next commit”)&lt;br /&gt;
* You commit those changes&lt;br /&gt;
&lt;br /&gt;
By commit, we mean creating a group of changes which gets given an identifier and a summary. We can then use this ID to revert or do something else with those changes later on.&lt;br /&gt;
&lt;br /&gt;
The summary or description just helps you understand what the changes are doing, so you can easily find a commit later on.&lt;br /&gt;
&lt;br /&gt;
=== Staging ===&lt;br /&gt;
&lt;br /&gt;
To stage something you can:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git add file/to/add # To stage one file at a time&lt;br /&gt;
git add .           # To stage all changes in current directory&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git status&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
To see the current unstaged and staged changes.&lt;br /&gt;
&lt;br /&gt;
If you were wanting to unstage all the changes, you can simply go:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git reset                 # to unstage all staged changes&lt;br /&gt;
git reset file/to/unstage # to unstage a particular file&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Unstaging does not delete the changes, it just removes it from the “staged” list (which is used when you run &amp;lt;code&amp;gt;commit&amp;lt;/code&amp;gt;)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Committing ===&lt;br /&gt;
&lt;br /&gt;
Once you have staged the changes you want in the next commit, you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git commit -m &amp;quot;Add setup to README.md&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which will commit the staged changes with the message “Add setup to README.md”. If you don’t specify a message, git will open your preferred text editor where you will be forced to add one.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
All commit summaries should be written in &#039;&#039;&#039;the present tense&#039;&#039;&#039; and should explain what the changes do.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Options:&lt;br /&gt;
&lt;br /&gt;
* Specify &amp;lt;code&amp;gt;-a&amp;lt;/code&amp;gt; to include all unstaged changes as well (except for new files)&lt;br /&gt;
* If you add a new line after the summary, you can add a more in-depth description.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_stage.gif|gif showing the process of adding a file and then staging and committing it]]&lt;br /&gt;
&lt;br /&gt;
=== Amending a commit ===&lt;br /&gt;
&lt;br /&gt;
This is more of an advanced command, but it can be helpful. You probably shouldn’t use this, because it will break things if other people have cloned the repository. If you want to add some changes to your previous commit, there is an &amp;lt;code&amp;gt;--amend&amp;lt;/code&amp;gt; option which will add all staged changes to the previous commit. It will also give you a chance to edit the commit message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git commit --amend&lt;br /&gt;
git commit --amend --no-edit # If you want to keep the same commit message&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As this changes the commit itself, if you have previously pushed that commit, you must then run &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt; (warning: this is a dangerous command).&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Generally, you should create a new commit rather than amending an existing one.&lt;br /&gt;
&lt;br /&gt;
Amending is only acceptable when you are working on your own personal branch, or if you have not yet pushed to the remote.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
If you were wanting to get the id of a commit or just see the history of changes, you can look at the log via going:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git log --oneline&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|text=&lt;br /&gt;
If &amp;lt;code&amp;gt;--oneline&amp;lt;/code&amp;gt; is not specified, it will show all the information about each commit on multiple lines (which is normally a lot harder to read)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
This will show you the start of the commit hash (or id) and the summary next to it, including what branch is at each commit.&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_log.gif|gif showing the output of running git log]]&lt;br /&gt;
&lt;br /&gt;
== Branching ==&lt;br /&gt;
&lt;br /&gt;
Branching is another key mechanic in git. Allowing multiple people to work on multiple different new features without getting in each others’ way.&lt;br /&gt;
&lt;br /&gt;
It basically creates a parallel copy of the codebase: a “branch”. You can then add commits to this copy without affecting the original branch. Once you are happy with the changes you can create a [[#merge-or-pull-request|merge or pull request]] to then merge it back into the original branch.&lt;br /&gt;
&lt;br /&gt;
Therefore the process for adding a feature on production is:&lt;br /&gt;
&lt;br /&gt;
* Create a branch from &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; (normally named something like &amp;lt;code&amp;gt;&amp;amp;lt;username&amp;amp;gt;/&amp;amp;lt;description&amp;amp;gt;&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;kebab-case&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add commits to fix the bug or add a feature&lt;br /&gt;
* Create a MR (or PR)&lt;br /&gt;
* Merge the branch back into &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Only maintainers can merge a branch back into &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; for security reasons and so you normally get one to “review” your MR.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Commands ===&lt;br /&gt;
&lt;br /&gt;
By default the initial branch should be &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; (you may see &amp;lt;code&amp;gt;master&amp;lt;/code&amp;gt; being used which is another option for the default name). This branch normally stores the stable release of the project.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git branch                  # List all local branch names&lt;br /&gt;
git branch branch-name      # Create a new branch from the current&lt;br /&gt;
git switch branch-name    # Switch to the branch &amp;quot;branch-name&amp;quot;&lt;br /&gt;
git switch -c branch-name # Create and switch to the branch &amp;quot;branch-name&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:git-switch_branch.gif|gif showing the process of creating and switching to a new branch]]&lt;br /&gt;
&lt;br /&gt;
Previously switching branches was incorporated into &amp;lt;code&amp;gt;checkout&amp;lt;/code&amp;gt; (which does a lot of other things), however &amp;lt;code&amp;gt;switch&amp;lt;/code&amp;gt; was designed to provide a cleaner interface for just switching branches and so we recommend using this instead.&lt;br /&gt;
&lt;br /&gt;
== Pushing &amp;amp;amp; Pulling ==&lt;br /&gt;
&lt;br /&gt;
If you have setup a remote repo, you will probably want to push and pull to it. Meaning: get (“pull”) the latest commits from it and send (“push”) your local commits to it.&lt;br /&gt;
&lt;br /&gt;
To push it is as simple as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git push&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, when pushing a branch for the first time (after its creation not cloning) you must set its upstream:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git push --set-upstream origin branch-name&lt;br /&gt;
# Or the shorthand:&lt;br /&gt;
git push -u origin branch-name&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_push.gif|gif showing the process of pushing the new branch to the remote repo]]&lt;br /&gt;
&lt;br /&gt;
This tells git that when running &amp;lt;code&amp;gt;git push&amp;lt;/code&amp;gt; you want to push to the remote named &amp;lt;code&amp;gt;origin&amp;lt;/code&amp;gt; by default.&lt;br /&gt;
&lt;br /&gt;
For pulling, its even simpler, just:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git pull&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Merge or Pull Request ==&lt;br /&gt;
&lt;br /&gt;
“Merge Requests” is what GitLab calls it and “Pull Requests” is what GitHub calls it.&lt;br /&gt;
&lt;br /&gt;
These are just items or “requests” where a user wants their changes merged into the &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; branch (or any branch for that matter).&lt;br /&gt;
&lt;br /&gt;
These are usually created and managed via GitHub or GitLab and allow another person to review your changes and merge them (if it is what they want).&lt;br /&gt;
&lt;br /&gt;
To see how to create an MR in GitLab see [[Git/GitLab#Merge Requests|the instructions here]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorial]]&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git|namespace=0|hideredirects=1}}&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/Advanced&amp;diff=138</id>
		<title>Git/Advanced</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/Advanced&amp;diff=138"/>
		<updated>2026-06-05T13:54:26Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* See also */ Don&amp;#039;t show redirects&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Git is a really powerful command line tool when you start looking into the advanced things you can do, but when maintaining or developing a project, you are probably not going to touch most of them. This is a list of ones which I use on a regular basis.&lt;br /&gt;
&lt;br /&gt;
It’s important to note that the more advanced commands can have quite powerful functions which can lose code if used incorrectly. However, in most cases git caches a significant amount, so if you lose a commit or some changes, &#039;&#039;&#039;don’t panic&#039;&#039;&#039; and look up your issue (someone has done this before and found a way to get the changes back).&lt;br /&gt;
&lt;br /&gt;
== More on Commit IDs ==&lt;br /&gt;
&lt;br /&gt;
Where it says to use the &amp;lt;code&amp;gt;commit-id&amp;lt;/code&amp;gt;, there are different inputs you can use to specify multiple commits easily.&lt;br /&gt;
&lt;br /&gt;
Let A and B be the ids for two different commits. To reference a range you can do:&lt;br /&gt;
&lt;br /&gt;
* A to B (including A): &amp;lt;code&amp;gt;A^..B&amp;lt;/code&amp;gt;&lt;br /&gt;
* A to B (excluding A): &amp;lt;code&amp;gt;A..B&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can specify the last &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; commits:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;HEAD~X&amp;lt;/code&amp;gt; e.g. &amp;lt;code&amp;gt;git reset --soft HEAD~4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Stashing ==&lt;br /&gt;
&lt;br /&gt;
Stashing is used when you want to record the current state of the working directory and the index, but want to go back to a clean working directory.&lt;br /&gt;
&lt;br /&gt;
It allows you to set aside some changes for later or just never commit them (without altering the [[Git/Special files#Gitignore|&amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file]]).&lt;br /&gt;
&lt;br /&gt;
By stashing your changes, they will be removed so you can no longer see them, but you can always &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;apply&amp;lt;/code&amp;gt; the stash to get the back at any point.&lt;br /&gt;
&lt;br /&gt;
=== Commands ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git stash       # Stash current unstaged (but tracked) changes&lt;br /&gt;
git stash apply # Restore the last stash without deleting it&lt;br /&gt;
git stash pop   # Restore the last stash and delete it&lt;br /&gt;
git stash list  # List the stashed changes&lt;br /&gt;
git stash show  # Inspect the stashed changes&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|type=reminder|text=&lt;br /&gt;
GUI tools e.g. lazygit or VSCode (with git extension pack) normally have a better interface for adding and removing stashes - so probably use that instead.&lt;br /&gt;
}}This is usually used with &amp;lt;code&amp;gt;git switch&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;git checkout&amp;lt;/code&amp;gt; to allow you to switch branches when you still have uncommitted changes that may conflict.&lt;br /&gt;
&lt;br /&gt;
== Reverting vs Resetting ==&lt;br /&gt;
&lt;br /&gt;
To either reset or revert, you need the git commit hash id (or the start of it), this can be done via &amp;lt;code&amp;gt;git log&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Once you have that, the interface is quite similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git reset commit-id&lt;br /&gt;
git revert commit-id&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
They both do the same thing of reverting the changes from the given commit. But the difference is, &amp;lt;code&amp;gt;revert&amp;lt;/code&amp;gt; will create a new commit (therefore you can just run &amp;lt;code&amp;gt;git push&amp;lt;/code&amp;gt; or merging without any issues), whereas &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; will remove those commits outright.&lt;br /&gt;
&lt;br /&gt;
When resetting, the tree (simply, the list of commits) itself is altered. This means that when pushing to a repo which already contains that commit, you have to use &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt;. This will cause anyone else working on the same branch to lose their changes, as they are forced to run &amp;lt;code&amp;gt;git pull --force&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This command is dangerous so make sure you check everything before using it as it forces the upstream to be exactly like your local branch. So if you accidentally removed the wrong commit, you cannot easily get it back.&lt;br /&gt;
&lt;br /&gt;
=== Extra options ===&lt;br /&gt;
&lt;br /&gt;
With &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; you also have extra options which you may find useful. You can either use &amp;lt;code&amp;gt;--soft&amp;lt;/code&amp;gt; (Put all the changes of the commit in staged) or &amp;lt;code&amp;gt;--hard&amp;lt;/code&amp;gt; (which is the default, just forget all changes).&lt;br /&gt;
&lt;br /&gt;
With &amp;lt;code&amp;gt;revert&amp;lt;/code&amp;gt; you can use &amp;lt;code&amp;gt;--no-commit&amp;lt;/code&amp;gt; which will put the inverse of the changes in staged (and not create a new commit). This allows you to add multiple reverts or more changes in it.&lt;br /&gt;
&lt;br /&gt;
== Rebasing ==&lt;br /&gt;
&lt;br /&gt;
Rebasing is used when commits have been added to &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; and you want to bring them to your branch (with was branched off of &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
To rebase off of main (when you are in your branch), you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase main&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|text=&lt;br /&gt;
As we are changing the structure of the commit tree, you must then run &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt; once you are 100% happy none of your changes have been lost.&lt;br /&gt;
&lt;br /&gt;
As usual, if there is anyone else using your current branch, you shouldn’t do this.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
The steps it is takes are:&lt;br /&gt;
&lt;br /&gt;
* Temporarily reset all your commits which you added to this branch&lt;br /&gt;
* Apply all the new commits in &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; (or the given branch)&lt;br /&gt;
* Re-apply all of your commits&lt;br /&gt;
&lt;br /&gt;
=== Dealing with Conflicts ===&lt;br /&gt;
&lt;br /&gt;
Sometimes there may be conflicting changes from the changes added to main (e.g. you’ve changed the same line as another change).&lt;br /&gt;
&lt;br /&gt;
This is when it gets quite confusing and dangerous.&lt;br /&gt;
&lt;br /&gt;
If this happens, git will print out an error, saying what files are are in conflict and where to find them.&lt;br /&gt;
&lt;br /&gt;
If you run &amp;lt;code&amp;gt;git status&amp;lt;/code&amp;gt; you will see that some files are staged and some are not. The idea for these are:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Staged files&#039;&#039;&#039;: These are the files which are not conflicting and will be committed on &amp;lt;code&amp;gt;git rebase --continue&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Unstaged files&#039;&#039;&#039;: These are the files with conflicts&lt;br /&gt;
&lt;br /&gt;
If you open one of the conflicting files you will find that git has altered it where the conflicts are.&lt;br /&gt;
&lt;br /&gt;
The format is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt; HEAD&lt;br /&gt;
- Auto writing README&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- who&#039;s the above guy?&lt;br /&gt;
=======&lt;br /&gt;
- something&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- other&lt;br /&gt;
&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt; 8f309e1 (Test commit)&amp;lt;/pre&amp;gt;&lt;br /&gt;
{{Note|text= class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
VSCode has its own custom interface for this [https://code.visualstudio.com/docs/sourcecontrol/overview#_3way-merge-editor read more here]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
You will see the changes you made are below the &amp;lt;code&amp;gt;=======&amp;lt;/code&amp;gt; and the (updated) upstream code in &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is above it.&lt;br /&gt;
&lt;br /&gt;
What you have to do is, for each of these conflicts, to choose which one to keep (or create a mixture). To do this, you just remove everything that shouldn’t be there (which includes &amp;lt;code&amp;gt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt; HEAD&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;=======&amp;lt;/code&amp;gt;, etc). For example, in this case it should result in:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;- Auto writing README&lt;br /&gt;
- something&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- other&lt;br /&gt;
- who&#039;s the above guy?&amp;lt;/pre&amp;gt;&lt;br /&gt;
Once you are happy an entire file is now conflict-free and correct, you can stage it.&lt;br /&gt;
&lt;br /&gt;
Then once there are no unstaged files left you can run &amp;lt;code&amp;gt;git rebase --continue&amp;lt;/code&amp;gt;, which will save the changes under the original commit (sometimes it will ask you to confirm the commit message with your editor, you can just save and exit it).&lt;br /&gt;
&lt;br /&gt;
Once you have finished rebasing, make sure to test that your code still works. It’s common for code to break after rebasing due to unexpected changes made by someone else.&lt;br /&gt;
&lt;br /&gt;
=== Interactive Rebase ===&lt;br /&gt;
&lt;br /&gt;
Interactive rebasing is one of the most powerful and fun commands ever. However it comes with the downside of it being quite dangerous.&lt;br /&gt;
&lt;br /&gt;
It is used for reorganising a merge request and managing commits to clean up the git log.&lt;br /&gt;
&lt;br /&gt;
To use it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase -i HEAD~X&lt;br /&gt;
# Or interactive rebase off of main&lt;br /&gt;
git rebase -i main&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This should bring up your default text editor with a list of commits with a list of commands at the end, which looks like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;pick af44004 Annoying rebasing commit&lt;br /&gt;
pick ceb4454 Add quote from Varnie&lt;br /&gt;
pick 1f156e8 Test commit&lt;br /&gt;
&lt;br /&gt;
# Rebase 315c076..1f156e8 onto 315c076 (3 commands)&lt;br /&gt;
#&lt;br /&gt;
# Commands:&lt;br /&gt;
# p, pick &amp;amp;lt;commit&amp;amp;gt; = use commit&lt;br /&gt;
# r, reword &amp;amp;lt;commit&amp;amp;gt; = use commit, but edit the commit message&lt;br /&gt;
# e, edit &amp;amp;lt;commit&amp;amp;gt; = use commit, but stop for amending&lt;br /&gt;
# s, squash &amp;amp;lt;commit&amp;amp;gt; = use commit, but meld into previous commit&lt;br /&gt;
# f, fixup [-C | -c] &amp;amp;lt;commit&amp;amp;gt; = like &amp;amp;quot;squash&amp;amp;quot; but keep only the previous&lt;br /&gt;
#                    commit&#039;s log message, unless -C is used, in which case&lt;br /&gt;
#                    keep only this commit&#039;s message; -c is same as -C but&lt;br /&gt;
#                    opens the editor&lt;br /&gt;
# x, exec &amp;amp;lt;command&amp;amp;gt; = run command (the rest of the line) using shell&lt;br /&gt;
# b, break = stop here (continue rebase later with &#039;git rebase --continue&#039;)&lt;br /&gt;
# d, drop &amp;amp;lt;commit&amp;amp;gt; = remove commit&lt;br /&gt;
# l, label &amp;amp;lt;label&amp;amp;gt; = label current HEAD with a name&lt;br /&gt;
# t, reset &amp;amp;lt;label&amp;amp;gt; = reset HEAD to a label&lt;br /&gt;
# m, merge [-C &amp;amp;lt;commit&amp;amp;gt; | -c &amp;amp;lt;commit&amp;amp;gt;] &amp;amp;lt;label&amp;amp;gt; [# &amp;amp;lt;oneline&amp;amp;gt;]&lt;br /&gt;
#         create a merge commit using the original merge commit&#039;s&lt;br /&gt;
#         message (or the oneline, if no original merge commit was&lt;br /&gt;
#         specified); use -c &amp;amp;lt;commit&amp;amp;gt; to reword the commit message&lt;br /&gt;
# u, update-ref &amp;amp;lt;ref&amp;amp;gt; = track a placeholder for the &amp;amp;lt;ref&amp;amp;gt; to be updated&lt;br /&gt;
#                       to this position in the new commits. The &amp;amp;lt;ref&amp;amp;gt; is&lt;br /&gt;
#                       updated at the end of the rebase&lt;br /&gt;
#&lt;br /&gt;
# These lines can be re-ordered; they are executed from top to bottom.&lt;br /&gt;
#&lt;br /&gt;
# If you remove a line here THAT COMMIT WILL BE LOST.&lt;br /&gt;
#&lt;br /&gt;
# However, if you remove everything, the rebase will be aborted.&lt;br /&gt;
#&amp;lt;/pre&amp;gt;&lt;br /&gt;
You can see all the commits which will be re-applied after the branch has been rolled back and commits from main been added.&lt;br /&gt;
&lt;br /&gt;
These all have the word &amp;lt;code&amp;gt;pick&amp;lt;/code&amp;gt; infront of them, meaning they will be committed as is, with the same message, and no editing happening.&lt;br /&gt;
&lt;br /&gt;
You can look over the other commands to see what you can do, but how it works is by replacing &amp;lt;code&amp;gt;pick&amp;lt;/code&amp;gt; with something else, e.g. &amp;lt;code&amp;gt;fixup&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;reword&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== The Fixup Commit ===&lt;br /&gt;
&lt;br /&gt;
This is a weird option when committing, that can be useful when you have an MR and you are wanting to keep a neat commit log, but are responding to review feedback.&lt;br /&gt;
&lt;br /&gt;
How this works, is lets say you’ve fixed something with a previous commit in your MR and were wanting to (when merging) squash this fix into that commit. But you want to have it as a separate commit to help the reviewer to see that you have fixed it.&lt;br /&gt;
&lt;br /&gt;
To do this you simply run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git commit --fixup=&amp;quot;amend:&amp;lt;git_commit_id&amp;gt;&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which will create a new commit with the message:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;amend! Add information for publicising events from BCSS and where to go&lt;br /&gt;
&lt;br /&gt;
&amp;amp;gt; Add information for publicising events from BCSS and where to go&amp;lt;/pre&amp;gt;&lt;br /&gt;
Which you can then push as its own commit.&lt;br /&gt;
&lt;br /&gt;
Then when you come to merge you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase -i main --autosquash&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which will automatically move your amend commit to be &amp;lt;code&amp;gt;fixup&amp;lt;/code&amp;gt; above the commit you were amending.&lt;br /&gt;
&lt;br /&gt;
== Merging ==&lt;br /&gt;
&lt;br /&gt;
Normally merging is done via the interface on the remote repository system you are using. However it can be done in the command line as well (it can also be used in place of &amp;lt;code&amp;gt;rebase&amp;lt;/code&amp;gt; so you don’t have to use the dangerous &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
It copies all the commits which have been added on a branch to the branch you are currently on (e.g. &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
So the process to merge a branch into main is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git checkout main&lt;br /&gt;
git merge branch-name&lt;br /&gt;
git branch -d branch-name # Deletes the branch If you don&#039;t need the branch any&lt;br /&gt;
                          # more&lt;br /&gt;
git push&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Conflicts can still happen, see [[Git/Advanced#dealing-with-conflicts|above]] for more information about how to manage them.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
When merging, cherry-picking or other, and you are dealing with conflicts you need to use the subcommand with &amp;lt;code&amp;gt;--continue&amp;lt;/code&amp;gt;, for example &amp;lt;code&amp;gt;git merge --continue&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;git cherry-pick --continue&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Cherry-picking ==&lt;br /&gt;
&lt;br /&gt;
Cherry picking allows you to bring a single commit (or multiple, see [[#more-on-commit-ids|here]]) from another branch to your current one.&lt;br /&gt;
&lt;br /&gt;
To do this, it is as simple as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git cherry-pick commit-id&lt;br /&gt;
git push&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Submodules ==&lt;br /&gt;
&lt;br /&gt;
Submodules are normally used in what is called a “monorepo”, a repo which stores multiple difference projects or git repositories.&lt;br /&gt;
&lt;br /&gt;
It is also useful for refactoring some files. E.g. if you need the same files in multiple different projects (e.g. standardised tests or config files), it is common to add a “meta” repo which stores these projects, then add this as a submodule to each project which uses it.&lt;br /&gt;
&lt;br /&gt;
To add a submodule you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git submodule add repo-url folder/to/store&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This will clone the module inside the folder &amp;lt;code&amp;gt;folder/to/store&amp;lt;/code&amp;gt; and will add a &amp;lt;code&amp;gt;.gitmodule&amp;lt;/code&amp;gt; file in the base of the repo.&lt;br /&gt;
&lt;br /&gt;
When cloning the repo on other devices, you must remember to recursively clone all the submodules as well via:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git clone --recurse-submodules -j8 project-url&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
&amp;lt;code&amp;gt;-j8&amp;lt;/code&amp;gt; is an performance optimisation see [https://stackoverflow.com/questions/3796927/how-do-i-git-clone-a-repo-including-its-submodules#4438292 this Stack Overflow answer]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Or if you have already cloned the repo and were wanting to update all the submodules, you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git submodule update --init --recursive&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Managing submodules ===&lt;br /&gt;
&lt;br /&gt;
Once you have cloned a submodule, you will note that any time you pull the latest changes to it, you need to make another commit in the base repo with the update.&lt;br /&gt;
&lt;br /&gt;
This is so that the submodules are locked on specific commits until you specifically say “yes this next commit is fine”.&lt;br /&gt;
&lt;br /&gt;
== Extra Configuration ==&lt;br /&gt;
&lt;br /&gt;
Sometimes there are configuration options that git will recommend when they become a problem, for example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git config --global pull.rebase true&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This means that when pulling from a branch which has new changes, it will rebase instead of merging the new commits.&lt;br /&gt;
&lt;br /&gt;
=== Commit Signing ===&lt;br /&gt;
&lt;br /&gt;
Commit signing is used to verify if you are who you say you are when committing (e.g. with your email address).&lt;br /&gt;
&lt;br /&gt;
I won’t go into much depth on this, instead just know it exists and is quite good practice to have but not necessary.&lt;br /&gt;
&lt;br /&gt;
You can read more [https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits here].&lt;br /&gt;
&lt;br /&gt;
Be aware that there are some consequences which come along with this:&lt;br /&gt;
&lt;br /&gt;
* If you have setup a strict mode with signing, you cannot commit if you loose access to your signing key&lt;br /&gt;
* When rebasing your changes, you have to resign all the commits (meaning you are the only one who can do it)&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git/|namespace=0|hideredirects=1}}&lt;br /&gt;
[[Category:Tutorial]]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git&amp;diff=137</id>
		<title>Git</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git&amp;diff=137"/>
		<updated>2026-06-05T13:53:55Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Don&amp;#039;t show redirects in see also&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[https://git-scm.com/ Git] is a “version control software” which can be [https://git-scm.com/downloads downloaded on their website]. It allows for version management:&lt;br /&gt;
&lt;br /&gt;
* Git keeps track and history of all changes and version of the software you are writing, and lets you:&lt;br /&gt;
** Revert and jump back and forth in history&lt;br /&gt;
** Create different versions or “branches” of your software&lt;br /&gt;
** Manage versions in a better way than “LATEST_COURSEWORK_X.zip”&lt;br /&gt;
** Easily see who last edited a line of code&lt;br /&gt;
* It makes collaborating on the same codebase easier&lt;br /&gt;
&lt;br /&gt;
A lot of programs have ways to help you interact with git so you don’t have to go through the terminal for most things. Some programs that integrate well:&lt;br /&gt;
&lt;br /&gt;
* VSCode&lt;br /&gt;
* All JetBrains products&lt;br /&gt;
* Vim with fugitive&lt;br /&gt;
* GitHub Desktop&lt;br /&gt;
&lt;br /&gt;
If you want a UI in the terminal on:&lt;br /&gt;
&lt;br /&gt;
* Lazygit (Linux/MacOS)&lt;br /&gt;
* GitUI (Linux/MacOS/Windows)&lt;br /&gt;
* And many more…&lt;br /&gt;
&lt;br /&gt;
Here we will mostly focus on the parts of git and using the command line tool as it more easily translates to any of the tools above. Plus for the more complicated routines, you can only do it via the terminal.&lt;br /&gt;
&lt;br /&gt;
== Alternative tutorials ==&lt;br /&gt;
This page is covers a lot of topics (including some commands which are a bit more advanced but useful for managing projects), and can be read all in one go or can just be used as reference. But there are also other resources which may be a bit more your speed:&lt;br /&gt;
&lt;br /&gt;
* [https://www.w3schools.com/git/default.asp W3Schools git tutorials]&lt;br /&gt;
* [https://www.atlassian.com/git/glossary Atlassians indepth git tutorials]&lt;br /&gt;
* [https://rogerdudler.github.io/git-guide/ The colourful intro]&lt;br /&gt;
* [https://code.visualstudio.com/docs/sourcecontrol/intro-to-git Git in VSCode]&lt;br /&gt;
* [https://ohshitgit.com/ Git Troubleshooting]&lt;br /&gt;
* [https://www.youtube.com/playlist?list=PLT4sucrjsqF8kvZFcRvxfhWuTvjKrXYzE BCSS Technical Labs]&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
When you first download git you need to do a bit of configuring before you start. This is just so when you start committing, it will come up with your name and email address.&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
git config user.email &amp;quot;your_email@abc.com&amp;quot;&lt;br /&gt;
git config user.name &amp;quot;Your Name&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;{{Note|text=Make sure the email is also linked to your account on GitHub or GitLab, see [[Git/GitLab|our GitLab documentation]].}}&lt;br /&gt;
You can also configure the name of the default branch to &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;master&amp;lt;/code&amp;gt;. [https://sfconservancy.org/news/2020/jun/23/gitbranchname/ See why].&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;&lt;br /&gt;
git config --global init.defaultBranch main&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Remote Repos ==&lt;br /&gt;
These are the web applications such as GitLab or GitHub. For bathcs.com, we use the [https://gitlab.bath.ac.uk|Bath’s GitLab instance] and have instructions [[Git/GitLab|here]]. But also note Bath has a GitHub enterprise instance running [https://github.bath.ac.uk/ here].&lt;br /&gt;
&lt;br /&gt;
They normally provide many features and tools for version control, collaboration and CI/CD.&lt;br /&gt;
&lt;br /&gt;
* You can &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; your local repository to them.&lt;br /&gt;
* Then on another device you can &amp;lt;code&amp;gt;pull&amp;lt;/code&amp;gt; this code from them.&lt;br /&gt;
* Collaborators that work on your code can also &amp;lt;code&amp;gt;push&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;pull&amp;lt;/code&amp;gt;&lt;br /&gt;
* You can collaborate with other people, on the same codebase&lt;br /&gt;
* Provides web UI for &amp;lt;code&amp;gt;git&amp;lt;/code&amp;gt;&lt;br /&gt;
* Acts as a “cloud backup”&lt;br /&gt;
&lt;br /&gt;
=== HTTP vs SSH ===&lt;br /&gt;
In the rest of the page, we will be using URLs to pull from remote repositories.&lt;br /&gt;
&lt;br /&gt;
On both GitHub and GitLab you can do this in two ways:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;http&amp;lt;/code&amp;gt;, where urls look like: &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;https://gitlab.bath.ac.uk/cs/wiki&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;ssh&amp;lt;/code&amp;gt;, where urls look like: &amp;lt;code&amp;gt;git@gitlab.bath.ac.uk:cs/wiki&amp;lt;/code&amp;gt;&lt;br /&gt;
{{Note|text=To convert from &amp;lt;code&amp;gt;http&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;ssh&amp;lt;/code&amp;gt;, you replace &amp;lt;code&amp;gt;https://&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;git@&amp;lt;/code&amp;gt; (which is the user we are sshing as) and then replace the first &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;:&amp;lt;/code&amp;gt;, specifying the folder location of the repo we are copying.&lt;br /&gt;
&lt;br /&gt;
It is exactly the same syntax as &amp;lt;code&amp;gt;scp&amp;lt;/code&amp;gt; (or ssh copy).}}&lt;br /&gt;
In most cases this does not matter. With public repositories &amp;lt;code&amp;gt;http&amp;lt;/code&amp;gt; does not require you to log in, however it cannot handle larger repos at lower internet speeds (as it times out).&lt;br /&gt;
&lt;br /&gt;
However when you have setup 2FA, you can’t just use your credentials to login, which is what &amp;lt;code&amp;gt;https&amp;lt;/code&amp;gt; normally requires. Therefore, you need to setup &amp;lt;code&amp;gt;ssh&amp;lt;/code&amp;gt; to work, which also requires you to have a [[SSH keys]].&lt;br /&gt;
&lt;br /&gt;
You can generate one by [[SSH keys|following our documentation]], which then you copy and paste the public key into the relavent section on GitHub or GitLab (see our [[Git/GitLab|GitLab documentation]], or the [https://docs.github.com/en/authentication/connecting-to-github-with-ssh/adding-a-new-ssh-key-to-your-github-account GitHub documentation]).&lt;br /&gt;
&lt;br /&gt;
Once you have set up this ssh key, you should be able to clone and push to a repo without specifying your login details.&lt;br /&gt;
&lt;br /&gt;
== Best Practices ==&lt;br /&gt;
&lt;br /&gt;
# Follow this workflow:&lt;br /&gt;
## Pull main&lt;br /&gt;
## Create a branch&lt;br /&gt;
## push&lt;br /&gt;
## Create a pull/merge request&lt;br /&gt;
## Merge into main&lt;br /&gt;
## Pull main&lt;br /&gt;
# Don’t push to main directly&lt;br /&gt;
# Add a &amp;lt;code&amp;gt;README.md&amp;lt;/code&amp;gt;&lt;br /&gt;
# Add &amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file: you can choose what files and folders git should ignore, for example, &amp;lt;code&amp;gt;.env&amp;lt;/code&amp;gt; file containing API keys.&lt;br /&gt;
# Write descriptive commit and PR/MR messages&lt;br /&gt;
# Git is simple in the basics, and very complicated but very powerful once you dive into it. Basics are enough to have version control. Use them!&lt;br /&gt;
# It’s hard to permanently lose data in git. Almost always you can restore in some way. Don’t panic. Use your favourite search engine or Dang It Git&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git/|namespace=0|hideredirects=1}}&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorial]]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Main_Page&amp;diff=136</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Main_Page&amp;diff=136"/>
		<updated>2026-06-05T13:51:46Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Protected &amp;quot;Main Page&amp;quot;: High traffic page ([Edit=Allow only autoconfirmed users] (indefinite) [Move=Allow only autoconfirmed users] (indefinite))&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Welcome to the wiki hosted by students of the University of Bath. It is managed by [[Bath Open Source Society]], and any student is free to login and edit pages. This is designed as a space where societies can store knowledge that can easily be passed down, or even students can share fun facts they have learnt while studying at Bath.&lt;br /&gt;
&lt;br /&gt;
There is minimal restriction to what can be uploaded to this wiki, we just ask that you be polite and follow the Dignity and Respect policy. All edits are associated with your University login and we will follow standard University procedures in the event of serious misconduct.&lt;br /&gt;
&lt;br /&gt;
If you would like help with anything, please contact [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk].&lt;br /&gt;
&lt;br /&gt;
== Contributing ==&lt;br /&gt;
To get started with this wiki, you just need to log in at the top right. You will then have permission to edit almost any page.&lt;br /&gt;
&lt;br /&gt;
For ideas of places you can contribute, see [[Special:WantedPages]] and [[:Category:Help needed]].&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/Basic_Components&amp;diff=135</id>
		<title>Git/Basic Components</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/Basic_Components&amp;diff=135"/>
		<updated>2026-06-05T11:57:52Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* Commands */ Add mention of git checkout&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Git is split up into multiple different subcommands. Here are a few essential ones to understand to use git.&lt;br /&gt;
&lt;br /&gt;
== Initialisation ==&lt;br /&gt;
&lt;br /&gt;
This is not entirely necessary, however it helps to understand what is happening when you clone a repository and the fact that a folder can be initialised with git without being reliant on a remote repository.&lt;br /&gt;
&lt;br /&gt;
When creating a new project locally, you can make the repository be version-controlled by running&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git init&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If you then run &amp;lt;code&amp;gt;git status&amp;lt;/code&amp;gt; you will see something like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;On branch main&lt;br /&gt;
nothing to commit, working tree clean&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_init.gif|gif showing what is explained in this section of initialising a git repo]]&lt;br /&gt;
&lt;br /&gt;
=== Adding a remote ===&lt;br /&gt;
&lt;br /&gt;
Adding a remote repository is not necessary unless you want to be pushing to one as some sort of backup or if you just want to share your code.&lt;br /&gt;
&lt;br /&gt;
To add one (you can also have multiple), you simply run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git remote add origin &amp;lt;url&amp;gt;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Here we are creating a remote called &amp;lt;code&amp;gt;origin&amp;lt;/code&amp;gt;, you can change this name if you have multiple. The default name is &amp;lt;code&amp;gt;origin&amp;lt;/code&amp;gt; and is what we will be using later on.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_remote_add.gif|gif showing the process of adding a remote repo and pulling from it]]&lt;br /&gt;
&lt;br /&gt;
== Cloning ==&lt;br /&gt;
&lt;br /&gt;
Cloning basically does everything we explained above automatically for you and then pulls the latest commits to your folder.&lt;br /&gt;
&lt;br /&gt;
The command to clone a repository is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git clone &amp;lt;url&amp;gt;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This clones the repo into the folder with the same name as the project. E.g. if I were to clone &amp;lt;code&amp;gt;git@gitlab.bath.ac.uk:cs/wiki&amp;lt;/code&amp;gt;, it would create a folder called &amp;lt;code&amp;gt;wiki&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
If you want a custom folder name, you just simply add another parameter e.g:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git clone git@gitlab.bath.ac.uk:cs/wiki bathcs_wiki&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_clone.gif|gif showing the process of cloning a repository]]&lt;br /&gt;
&lt;br /&gt;
== Staging and Committing ==&lt;br /&gt;
&lt;br /&gt;
Staging and committing are essential parts of git. The process goes:&lt;br /&gt;
&lt;br /&gt;
* You make some changes&lt;br /&gt;
* You stage those changes (basically saying “yes I want these changes to be in the next commit”)&lt;br /&gt;
* You commit those changes&lt;br /&gt;
&lt;br /&gt;
By commit, we mean creating a group of changes which gets given an identifier and a summary. We can then use this ID to revert or do something else with those changes later on.&lt;br /&gt;
&lt;br /&gt;
The summary or description just helps you understand what the changes are doing, so you can easily find a commit later on.&lt;br /&gt;
&lt;br /&gt;
=== Staging ===&lt;br /&gt;
&lt;br /&gt;
To stage something you can:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git add file/to/add # To stage one file at a time&lt;br /&gt;
git add .           # To stage all changes in current directory&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git status&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
To see the current unstaged and staged changes.&lt;br /&gt;
&lt;br /&gt;
If you were wanting to unstage all the changes, you can simply go:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git reset                 # to unstage all staged changes&lt;br /&gt;
git reset file/to/unstage # to unstage a particular file&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Unstaging does not delete the changes, it just removes it from the “staged” list (which is used when you run &amp;lt;code&amp;gt;commit&amp;lt;/code&amp;gt;)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Committing ===&lt;br /&gt;
&lt;br /&gt;
Once you have staged the changes you want in the next commit, you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git commit -m &amp;quot;Add setup to README.md&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which will commit the staged changes with the message “Add setup to README.md”. If you don’t specify a message, git will open your preferred text editor where you will be forced to add one.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
All commit summaries should be written in &#039;&#039;&#039;the present tense&#039;&#039;&#039; and should explain what the changes do.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Options:&lt;br /&gt;
&lt;br /&gt;
* Specify &amp;lt;code&amp;gt;-a&amp;lt;/code&amp;gt; to include all unstaged changes as well (except for new files)&lt;br /&gt;
* If you add a new line after the summary, you can add a more in-depth description.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_stage.gif|gif showing the process of adding a file and then staging and committing it]]&lt;br /&gt;
&lt;br /&gt;
=== Amending a commit ===&lt;br /&gt;
&lt;br /&gt;
This is more of an advanced command, but it can be helpful. You probably shouldn’t use this, because it will break things if other people have cloned the repository. If you want to add some changes to your previous commit, there is an &amp;lt;code&amp;gt;--amend&amp;lt;/code&amp;gt; option which will add all staged changes to the previous commit. It will also give you a chance to edit the commit message.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git commit --amend&lt;br /&gt;
git commit --amend --no-edit # If you want to keep the same commit message&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
As this changes the commit itself, if you have previously pushed that commit, you must then run &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt; (warning: this is a dangerous command).&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Generally, you should create a new commit rather than amending an existing one.&lt;br /&gt;
&lt;br /&gt;
Amending is only acceptable when you are working on your own personal branch, or if you have not yet pushed to the remote.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
If you were wanting to get the id of a commit or just see the history of changes, you can look at the log via going:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git log --oneline&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|text=&lt;br /&gt;
If &amp;lt;code&amp;gt;--oneline&amp;lt;/code&amp;gt; is not specified, it will show all the information about each commit on multiple lines (which is normally a lot harder to read)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
This will show you the start of the commit hash (or id) and the summary next to it, including what branch is at each commit.&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_log.gif|gif showing the output of running git log]]&lt;br /&gt;
&lt;br /&gt;
== Branching ==&lt;br /&gt;
&lt;br /&gt;
Branching is another key mechanic in git. Allowing multiple people to work on multiple different new features without getting in each others’ way.&lt;br /&gt;
&lt;br /&gt;
It basically creates a parallel copy of the codebase: a “branch”. You can then add commits to this copy without affecting the original branch. Once you are happy with the changes you can create a [[#merge-or-pull-request|merge or pull request]] to then merge it back into the original branch.&lt;br /&gt;
&lt;br /&gt;
Therefore the process for adding a feature on production is:&lt;br /&gt;
&lt;br /&gt;
* Create a branch from &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; (normally named something like &amp;lt;code&amp;gt;&amp;amp;lt;username&amp;amp;gt;/&amp;amp;lt;description&amp;amp;gt;&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;kebab-case&amp;lt;/code&amp;gt;)&lt;br /&gt;
* Add commits to fix the bug or add a feature&lt;br /&gt;
* Create a MR (or PR)&lt;br /&gt;
* Merge the branch back into &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Only maintainers can merge a branch back into &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; for security reasons and so you normally get one to “review” your MR.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Commands ===&lt;br /&gt;
&lt;br /&gt;
By default the initial branch should be &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; (you may see &amp;lt;code&amp;gt;master&amp;lt;/code&amp;gt; being used which is another option for the default name). This branch normally stores the stable release of the project.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git branch                  # List all local branch names&lt;br /&gt;
git branch branch-name      # Create a new branch from the current&lt;br /&gt;
git switch branch-name    # Switch to the branch &amp;quot;branch-name&amp;quot;&lt;br /&gt;
git switch -c branch-name # Create and switch to the branch &amp;quot;branch-name&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:git-switch_branch.gif|gif showing the process of creating and switching to a new branch]]&lt;br /&gt;
&lt;br /&gt;
Previously switching branches was incorporated into &amp;lt;code&amp;gt;checkout&amp;lt;/code&amp;gt; (which does a lot of other things), however &amp;lt;code&amp;gt;switch&amp;lt;/code&amp;gt; was designed to provide a cleaner interface for just switching branches and so we recommend using this instead.&lt;br /&gt;
&lt;br /&gt;
== Pushing &amp;amp;amp; Pulling ==&lt;br /&gt;
&lt;br /&gt;
If you have setup a remote repo, you will probably want to push and pull to it. Meaning: get (“pull”) the latest commits from it and send (“push”) your local commits to it.&lt;br /&gt;
&lt;br /&gt;
To push it is as simple as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git push&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, when pushing a branch for the first time (after its creation not cloning) you must set its upstream:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git push --set-upstream origin branch-name&lt;br /&gt;
# Or the shorthand:&lt;br /&gt;
git push -u origin branch-name&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[File:git-git_push.gif|gif showing the process of pushing the new branch to the remote repo]]&lt;br /&gt;
&lt;br /&gt;
This tells git that when running &amp;lt;code&amp;gt;git push&amp;lt;/code&amp;gt; you want to push to the remote named &amp;lt;code&amp;gt;origin&amp;lt;/code&amp;gt; by default.&lt;br /&gt;
&lt;br /&gt;
For pulling, its even simpler, just:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git pull&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Merge or Pull Request ==&lt;br /&gt;
&lt;br /&gt;
“Merge Requests” is what GitLab calls it and “Pull Requests” is what GitHub calls it.&lt;br /&gt;
&lt;br /&gt;
These are just items or “requests” where a user wants their changes merged into the &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; branch (or any branch for that matter).&lt;br /&gt;
&lt;br /&gt;
These are usually created and managed via GitHub or GitLab and allow another person to review your changes and merge them (if it is what they want).&lt;br /&gt;
&lt;br /&gt;
To see how to create an MR in GitLab see [[Git/GitLab#Merge Requests|the instructions here]].&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Tutorial]]&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git|namespace=0}}&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Git/Advanced&amp;diff=134</id>
		<title>Git/Advanced</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Git/Advanced&amp;diff=134"/>
		<updated>2026-06-05T11:55:42Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* Stashing */ Clarify uses for switch and checkout within stashing&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Git is a really powerful command line tool when you start looking into the advanced things you can do, but when maintaining or developing a project, you are probably not going to touch most of them. This is a list of ones which I use on a regular basis.&lt;br /&gt;
&lt;br /&gt;
It’s important to note that the more advanced commands can have quite powerful functions which can lose code if used incorrectly. However, in most cases git caches a significant amount, so if you lose a commit or some changes, &#039;&#039;&#039;don’t panic&#039;&#039;&#039; and look up your issue (someone has done this before and found a way to get the changes back).&lt;br /&gt;
&lt;br /&gt;
== More on Commit IDs ==&lt;br /&gt;
&lt;br /&gt;
Where it says to use the &amp;lt;code&amp;gt;commit-id&amp;lt;/code&amp;gt;, there are different inputs you can use to specify multiple commits easily.&lt;br /&gt;
&lt;br /&gt;
Let A and B be the ids for two different commits. To reference a range you can do:&lt;br /&gt;
&lt;br /&gt;
* A to B (including A): &amp;lt;code&amp;gt;A^..B&amp;lt;/code&amp;gt;&lt;br /&gt;
* A to B (excluding A): &amp;lt;code&amp;gt;A..B&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or you can specify the last &amp;lt;code&amp;gt;X&amp;lt;/code&amp;gt; commits:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;HEAD~X&amp;lt;/code&amp;gt; e.g. &amp;lt;code&amp;gt;git reset --soft HEAD~4&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Stashing ==&lt;br /&gt;
&lt;br /&gt;
Stashing is used when you want to record the current state of the working directory and the index, but want to go back to a clean working directory.&lt;br /&gt;
&lt;br /&gt;
It allows you to set aside some changes for later or just never commit them (without altering the [[Git/Special files#Gitignore|&amp;lt;code&amp;gt;.gitignore&amp;lt;/code&amp;gt; file]]).&lt;br /&gt;
&lt;br /&gt;
By stashing your changes, they will be removed so you can no longer see them, but you can always &amp;lt;code&amp;gt;pop&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;apply&amp;lt;/code&amp;gt; the stash to get the back at any point.&lt;br /&gt;
&lt;br /&gt;
=== Commands ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git stash       # Stash current unstaged (but tracked) changes&lt;br /&gt;
git stash apply # Restore the last stash without deleting it&lt;br /&gt;
git stash pop   # Restore the last stash and delete it&lt;br /&gt;
git stash list  # List the stashed changes&lt;br /&gt;
git stash show  # Inspect the stashed changes&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|type=reminder|text=&lt;br /&gt;
GUI tools e.g. lazygit or VSCode (with git extension pack) normally have a better interface for adding and removing stashes - so probably use that instead.&lt;br /&gt;
}}This is usually used with &amp;lt;code&amp;gt;git switch&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;git checkout&amp;lt;/code&amp;gt; to allow you to switch branches when you still have uncommitted changes that may conflict.&lt;br /&gt;
&lt;br /&gt;
== Reverting vs Resetting ==&lt;br /&gt;
&lt;br /&gt;
To either reset or revert, you need the git commit hash id (or the start of it), this can be done via &amp;lt;code&amp;gt;git log&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Once you have that, the interface is quite similar:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git reset commit-id&lt;br /&gt;
git revert commit-id&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
They both do the same thing of reverting the changes from the given commit. But the difference is, &amp;lt;code&amp;gt;revert&amp;lt;/code&amp;gt; will create a new commit (therefore you can just run &amp;lt;code&amp;gt;git push&amp;lt;/code&amp;gt; or merging without any issues), whereas &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; will remove those commits outright.&lt;br /&gt;
&lt;br /&gt;
When resetting, the tree (simply, the list of commits) itself is altered. This means that when pushing to a repo which already contains that commit, you have to use &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt;. This will cause anyone else working on the same branch to lose their changes, as they are forced to run &amp;lt;code&amp;gt;git pull --force&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This command is dangerous so make sure you check everything before using it as it forces the upstream to be exactly like your local branch. So if you accidentally removed the wrong commit, you cannot easily get it back.&lt;br /&gt;
&lt;br /&gt;
=== Extra options ===&lt;br /&gt;
&lt;br /&gt;
With &amp;lt;code&amp;gt;reset&amp;lt;/code&amp;gt; you also have extra options which you may find useful. You can either use &amp;lt;code&amp;gt;--soft&amp;lt;/code&amp;gt; (Put all the changes of the commit in staged) or &amp;lt;code&amp;gt;--hard&amp;lt;/code&amp;gt; (which is the default, just forget all changes).&lt;br /&gt;
&lt;br /&gt;
With &amp;lt;code&amp;gt;revert&amp;lt;/code&amp;gt; you can use &amp;lt;code&amp;gt;--no-commit&amp;lt;/code&amp;gt; which will put the inverse of the changes in staged (and not create a new commit). This allows you to add multiple reverts or more changes in it.&lt;br /&gt;
&lt;br /&gt;
== Rebasing ==&lt;br /&gt;
&lt;br /&gt;
Rebasing is used when commits have been added to &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; and you want to bring them to your branch (with was branched off of &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
To rebase off of main (when you are in your branch), you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase main&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
{{Note|text=&lt;br /&gt;
As we are changing the structure of the commit tree, you must then run &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt; once you are 100% happy none of your changes have been lost.&lt;br /&gt;
&lt;br /&gt;
As usual, if there is anyone else using your current branch, you shouldn’t do this.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
The steps it is takes are:&lt;br /&gt;
&lt;br /&gt;
* Temporarily reset all your commits which you added to this branch&lt;br /&gt;
* Apply all the new commits in &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; (or the given branch)&lt;br /&gt;
* Re-apply all of your commits&lt;br /&gt;
&lt;br /&gt;
=== Dealing with Conflicts ===&lt;br /&gt;
&lt;br /&gt;
Sometimes there may be conflicting changes from the changes added to main (e.g. you’ve changed the same line as another change).&lt;br /&gt;
&lt;br /&gt;
This is when it gets quite confusing and dangerous.&lt;br /&gt;
&lt;br /&gt;
If this happens, git will print out an error, saying what files are are in conflict and where to find them.&lt;br /&gt;
&lt;br /&gt;
If you run &amp;lt;code&amp;gt;git status&amp;lt;/code&amp;gt; you will see that some files are staged and some are not. The idea for these are:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Staged files&#039;&#039;&#039;: These are the files which are not conflicting and will be committed on &amp;lt;code&amp;gt;git rebase --continue&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Unstaged files&#039;&#039;&#039;: These are the files with conflicts&lt;br /&gt;
&lt;br /&gt;
If you open one of the conflicting files you will find that git has altered it where the conflicts are.&lt;br /&gt;
&lt;br /&gt;
The format is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt; HEAD&lt;br /&gt;
- Auto writing README&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- who&#039;s the above guy?&lt;br /&gt;
=======&lt;br /&gt;
- something&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- other&lt;br /&gt;
&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt;&amp;amp;gt; 8f309e1 (Test commit)&amp;lt;/pre&amp;gt;&lt;br /&gt;
{{Note|text= class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
VSCode has its own custom interface for this [https://code.visualstudio.com/docs/sourcecontrol/overview#_3way-merge-editor read more here]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
You will see the changes you made are below the &amp;lt;code&amp;gt;=======&amp;lt;/code&amp;gt; and the (updated) upstream code in &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; is above it.&lt;br /&gt;
&lt;br /&gt;
What you have to do is, for each of these conflicts, to choose which one to keep (or create a mixture). To do this, you just remove everything that shouldn’t be there (which includes &amp;lt;code&amp;gt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt;&amp;amp;lt; HEAD&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;=======&amp;lt;/code&amp;gt;, etc). For example, in this case it should result in:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;- Auto writing README&lt;br /&gt;
- something&lt;br /&gt;
- A cool logo - hw&lt;br /&gt;
- other&lt;br /&gt;
- who&#039;s the above guy?&amp;lt;/pre&amp;gt;&lt;br /&gt;
Once you are happy an entire file is now conflict-free and correct, you can stage it.&lt;br /&gt;
&lt;br /&gt;
Then once there are no unstaged files left you can run &amp;lt;code&amp;gt;git rebase --continue&amp;lt;/code&amp;gt;, which will save the changes under the original commit (sometimes it will ask you to confirm the commit message with your editor, you can just save and exit it).&lt;br /&gt;
&lt;br /&gt;
Once you have finished rebasing, make sure to test that your code still works. It’s common for code to break after rebasing due to unexpected changes made by someone else.&lt;br /&gt;
&lt;br /&gt;
=== Interactive Rebase ===&lt;br /&gt;
&lt;br /&gt;
Interactive rebasing is one of the most powerful and fun commands ever. However it comes with the downside of it being quite dangerous.&lt;br /&gt;
&lt;br /&gt;
It is used for reorganising a merge request and managing commits to clean up the git log.&lt;br /&gt;
&lt;br /&gt;
To use it:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase -i HEAD~X&lt;br /&gt;
# Or interactive rebase off of main&lt;br /&gt;
git rebase -i main&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This should bring up your default text editor with a list of commits with a list of commands at the end, which looks like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;pick af44004 Annoying rebasing commit&lt;br /&gt;
pick ceb4454 Add quote from Varnie&lt;br /&gt;
pick 1f156e8 Test commit&lt;br /&gt;
&lt;br /&gt;
# Rebase 315c076..1f156e8 onto 315c076 (3 commands)&lt;br /&gt;
#&lt;br /&gt;
# Commands:&lt;br /&gt;
# p, pick &amp;amp;lt;commit&amp;amp;gt; = use commit&lt;br /&gt;
# r, reword &amp;amp;lt;commit&amp;amp;gt; = use commit, but edit the commit message&lt;br /&gt;
# e, edit &amp;amp;lt;commit&amp;amp;gt; = use commit, but stop for amending&lt;br /&gt;
# s, squash &amp;amp;lt;commit&amp;amp;gt; = use commit, but meld into previous commit&lt;br /&gt;
# f, fixup [-C | -c] &amp;amp;lt;commit&amp;amp;gt; = like &amp;amp;quot;squash&amp;amp;quot; but keep only the previous&lt;br /&gt;
#                    commit&#039;s log message, unless -C is used, in which case&lt;br /&gt;
#                    keep only this commit&#039;s message; -c is same as -C but&lt;br /&gt;
#                    opens the editor&lt;br /&gt;
# x, exec &amp;amp;lt;command&amp;amp;gt; = run command (the rest of the line) using shell&lt;br /&gt;
# b, break = stop here (continue rebase later with &#039;git rebase --continue&#039;)&lt;br /&gt;
# d, drop &amp;amp;lt;commit&amp;amp;gt; = remove commit&lt;br /&gt;
# l, label &amp;amp;lt;label&amp;amp;gt; = label current HEAD with a name&lt;br /&gt;
# t, reset &amp;amp;lt;label&amp;amp;gt; = reset HEAD to a label&lt;br /&gt;
# m, merge [-C &amp;amp;lt;commit&amp;amp;gt; | -c &amp;amp;lt;commit&amp;amp;gt;] &amp;amp;lt;label&amp;amp;gt; [# &amp;amp;lt;oneline&amp;amp;gt;]&lt;br /&gt;
#         create a merge commit using the original merge commit&#039;s&lt;br /&gt;
#         message (or the oneline, if no original merge commit was&lt;br /&gt;
#         specified); use -c &amp;amp;lt;commit&amp;amp;gt; to reword the commit message&lt;br /&gt;
# u, update-ref &amp;amp;lt;ref&amp;amp;gt; = track a placeholder for the &amp;amp;lt;ref&amp;amp;gt; to be updated&lt;br /&gt;
#                       to this position in the new commits. The &amp;amp;lt;ref&amp;amp;gt; is&lt;br /&gt;
#                       updated at the end of the rebase&lt;br /&gt;
#&lt;br /&gt;
# These lines can be re-ordered; they are executed from top to bottom.&lt;br /&gt;
#&lt;br /&gt;
# If you remove a line here THAT COMMIT WILL BE LOST.&lt;br /&gt;
#&lt;br /&gt;
# However, if you remove everything, the rebase will be aborted.&lt;br /&gt;
#&amp;lt;/pre&amp;gt;&lt;br /&gt;
You can see all the commits which will be re-applied after the branch has been rolled back and commits from main been added.&lt;br /&gt;
&lt;br /&gt;
These all have the word &amp;lt;code&amp;gt;pick&amp;lt;/code&amp;gt; infront of them, meaning they will be committed as is, with the same message, and no editing happening.&lt;br /&gt;
&lt;br /&gt;
You can look over the other commands to see what you can do, but how it works is by replacing &amp;lt;code&amp;gt;pick&amp;lt;/code&amp;gt; with something else, e.g. &amp;lt;code&amp;gt;fixup&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;reword&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== The Fixup Commit ===&lt;br /&gt;
&lt;br /&gt;
This is a weird option when committing, that can be useful when you have an MR and you are wanting to keep a neat commit log, but are responding to review feedback.&lt;br /&gt;
&lt;br /&gt;
How this works, is lets say you’ve fixed something with a previous commit in your MR and were wanting to (when merging) squash this fix into that commit. But you want to have it as a separate commit to help the reviewer to see that you have fixed it.&lt;br /&gt;
&lt;br /&gt;
To do this you simply run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git commit --fixup=&amp;quot;amend:&amp;lt;git_commit_id&amp;gt;&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which will create a new commit with the message:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;amend! Add information for publicising events from BCSS and where to go&lt;br /&gt;
&lt;br /&gt;
&amp;amp;gt; Add information for publicising events from BCSS and where to go&amp;lt;/pre&amp;gt;&lt;br /&gt;
Which you can then push as its own commit.&lt;br /&gt;
&lt;br /&gt;
Then when you come to merge you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git rebase -i main --autosquash&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which will automatically move your amend commit to be &amp;lt;code&amp;gt;fixup&amp;lt;/code&amp;gt; above the commit you were amending.&lt;br /&gt;
&lt;br /&gt;
== Merging ==&lt;br /&gt;
&lt;br /&gt;
Normally merging is done via the interface on the remote repository system you are using. However it can be done in the command line as well (it can also be used in place of &amp;lt;code&amp;gt;rebase&amp;lt;/code&amp;gt; so you don’t have to use the dangerous &amp;lt;code&amp;gt;git push --force&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
It copies all the commits which have been added on a branch to the branch you are currently on (e.g. &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
So the process to merge a branch into main is:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git checkout main&lt;br /&gt;
git merge branch-name&lt;br /&gt;
git branch -d branch-name # Deletes the branch If you don&#039;t need the branch any&lt;br /&gt;
                          # more&lt;br /&gt;
git push&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Conflicts can still happen, see [[Git/Advanced#dealing-with-conflicts|above]] for more information about how to manage them.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
When merging, cherry-picking or other, and you are dealing with conflicts you need to use the subcommand with &amp;lt;code&amp;gt;--continue&amp;lt;/code&amp;gt;, for example &amp;lt;code&amp;gt;git merge --continue&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;git cherry-pick --continue&amp;lt;/code&amp;gt;.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Cherry-picking ==&lt;br /&gt;
&lt;br /&gt;
Cherry picking allows you to bring a single commit (or multiple, see [[#more-on-commit-ids|here]]) from another branch to your current one.&lt;br /&gt;
&lt;br /&gt;
To do this, it is as simple as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git cherry-pick commit-id&lt;br /&gt;
git push&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
== Submodules ==&lt;br /&gt;
&lt;br /&gt;
Submodules are normally used in what is called a “monorepo”, a repo which stores multiple difference projects or git repositories.&lt;br /&gt;
&lt;br /&gt;
It is also useful for refactoring some files. E.g. if you need the same files in multiple different projects (e.g. standardised tests or config files), it is common to add a “meta” repo which stores these projects, then add this as a submodule to each project which uses it.&lt;br /&gt;
&lt;br /&gt;
To add a submodule you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git submodule add repo-url folder/to/store&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This will clone the module inside the folder &amp;lt;code&amp;gt;folder/to/store&amp;lt;/code&amp;gt; and will add a &amp;lt;code&amp;gt;.gitmodule&amp;lt;/code&amp;gt; file in the base of the repo.&lt;br /&gt;
&lt;br /&gt;
When cloning the repo on other devices, you must remember to recursively clone all the submodules as well via:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git clone --recurse-submodules -j8 project-url&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
&amp;lt;code&amp;gt;-j8&amp;lt;/code&amp;gt; is an performance optimisation see [https://stackoverflow.com/questions/3796927/how-do-i-git-clone-a-repo-including-its-submodules#4438292 this Stack Overflow answer]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Or if you have already cloned the repo and were wanting to update all the submodules, you can run:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git submodule update --init --recursive&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Managing submodules ===&lt;br /&gt;
&lt;br /&gt;
Once you have cloned a submodule, you will note that any time you pull the latest changes to it, you need to make another commit in the base repo with the update.&lt;br /&gt;
&lt;br /&gt;
This is so that the submodules are locked on specific commits until you specifically say “yes this next commit is fine”.&lt;br /&gt;
&lt;br /&gt;
== Extra Configuration ==&lt;br /&gt;
&lt;br /&gt;
Sometimes there are configuration options that git will recommend when they become a problem, for example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;git config --global pull.rebase true&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This means that when pulling from a branch which has new changes, it will rebase instead of merging the new commits.&lt;br /&gt;
&lt;br /&gt;
=== Commit Signing ===&lt;br /&gt;
&lt;br /&gt;
Commit signing is used to verify if you are who you say you are when committing (e.g. with your email address).&lt;br /&gt;
&lt;br /&gt;
I won’t go into much depth on this, instead just know it exists and is quite good practice to have but not necessary.&lt;br /&gt;
&lt;br /&gt;
You can read more [https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits here].&lt;br /&gt;
&lt;br /&gt;
Be aware that there are some consequences which come along with this:&lt;br /&gt;
&lt;br /&gt;
* If you have setup a strict mode with signing, you cannot commit if you loose access to your signing key&lt;br /&gt;
* When rebasing your changes, you have to resign all the commits (meaning you are the only one who can do it)&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
{{Special:PrefixIndex|prefix=Git|namespace=0}}&lt;br /&gt;
[[Category:Tutorial]]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=133</id>
		<title>Kubernetes</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=133"/>
		<updated>2026-06-04T14:20:33Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Add traefik and gateways section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== General knowledge ==&lt;br /&gt;
This tries to cover some basic concepts, focusing on common confusion, but it will skip over a lot of the general knowledge information such as secrets and configmaps. The kubernete&#039;s documentation is pretty good, though difficult to read at some points, but there are loads of great tutorials explaining how kubernetes works.&lt;br /&gt;
&lt;br /&gt;
=== Pod vs Container ===&lt;br /&gt;
A common confusion is that [https://kubernetes.io/docs/concepts/workloads/pods/ pod]&#039;s are containers in kubernetes. This is not exactly true, a pod is a general group of linux namespaces which can host multiple containers. This means you can have a container that writes to a directory and another container that reads from that directory in the same pod. This can be very powerful, but in a lot of cases can be ignored.&lt;br /&gt;
&lt;br /&gt;
But it is key to point out that a Pod is a resource that is created by other kubernetes resources. They are a group of processes running, once they die the pod is deleted and forgotten about. Therefore you should not be creating pods directly, instead you should be using deployments, statefulsets, cronjobs or even jobs. All these resources create generate a pod as their lifecycle and will restart/recreate the pod if it fails.&lt;br /&gt;
&lt;br /&gt;
=== Statefulset vs deployment ===&lt;br /&gt;
Another key understanding is the difference between statefulsets and deployments, as statefulsets can cause some confusion in how they work. The difference is more applicable to multinode clusters but are still key to the structure of kubernetes.&lt;br /&gt;
&lt;br /&gt;
Effectively, a statefulset is a deployment with writable volumes - known as persistent volumes (PV). Having the ability to write to volumes can cause race conditions when multiple pods across nodes are writing to the same file. This is where statefulsets come in, they lock volumes and so they can only be used by one node and one pod, with scaling creating new persistant volumes which are stored separately. This means that if you scale a statefulset that relies on shared knowledge in the volume, half your requests will have one set of data and the other half will have another.&lt;br /&gt;
&lt;br /&gt;
This obviously is quite a big disadvantage and can lead to confusing behaviour when a node is not configured to shutdown safely and taint itself, moving all the statefulsets off of itself before it shutsdown - if PV is locked by a node and pod, it cannot be deployed to another cluster.&lt;br /&gt;
&lt;br /&gt;
Therefore, this is where deployments come in, they, usually, do not have associated persistent volumes, allowing for easy horizontal scaling. For storing shared data, they should connect to a database on another node which can be more compatible with statefulsets when configured correctly.&lt;br /&gt;
&lt;br /&gt;
Both of these resources will create pods and redeploy them if they crash.&lt;br /&gt;
&lt;br /&gt;
==== Liveness/Startup probes ====&lt;br /&gt;
Liveness and startup probes can be defined on pods, and these let kubernetes know if a pod has started correctly and if it still is alive. For example, some deployments might take a while to start up and configure everything before it starts serving content and so when restarting, this can cause some downtime. Downtime is what we are trying to avoid and so by using a startup probe, kubernetes knows that this application is ready, and so it will only terminate the previous node once the new one is started up resulting in zero downtime!&lt;br /&gt;
&lt;br /&gt;
The liveness probe on the other hand periodically checks whether the pod is still alive. This means that if it suddenly stops responding due to a long database query, kubernetes can detect that and replace the pod with another further reducing downtime. However, this usually suggests something else is wrong with the application and so this should be investigated and fixed.&lt;br /&gt;
&lt;br /&gt;
==== Security Context ====&lt;br /&gt;
{{Note|text=Within [[BOSS/Hosting/Cluster|BOSS&#039;s kubernetes cluster]], we define a security policy which requires all pods to correctly define their security context and make sure that it is not running as root.}}&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ security context] defines what privileges the pod has when running, we effectively want this to be as minimal as possible to reduce attack surface area. E.g.&lt;br /&gt;
&lt;br /&gt;
* Run as user&lt;br /&gt;
* Don&#039;t allow privilege escalation&lt;br /&gt;
* Properly define seccomp policy&lt;br /&gt;
* Default SELinux container context&lt;br /&gt;
* Drop all capabilities&lt;br /&gt;
&lt;br /&gt;
However this can cause issues with third-party applications which commonly do some questionable things, e.g. require running as root or changing the uid. But for our pods you can mostly just copy and paste:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  # ...&lt;br /&gt;
  spec {&lt;br /&gt;
    # ...&lt;br /&gt;
    template {&lt;br /&gt;
      # ...&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          # ...&lt;br /&gt;
&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          # ...&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[Terraform]] for more information.&lt;br /&gt;
&lt;br /&gt;
=== What is a CRD? ===&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ Custom Resource Definition (CRD)], allows you to extend kubernetes capabilities and define custome resources. This is usually paired with an operator which reads the resources and performs some actions.&lt;br /&gt;
&lt;br /&gt;
We should never create our own, but third-party ones make it much easier for doing things such as creating ingress routes with traefik or define database clusters with our postgres operator.&lt;br /&gt;
&lt;br /&gt;
K9s and kubectl support these out of the box (as they are basically just schemas for yaml configuration), and you can see all pods by using the name of the resource.&lt;br /&gt;
&lt;br /&gt;
=== Traefik and gateways ===&lt;br /&gt;
[[File:Gateway diagram.svg|thumb|368x368px|A digram depicting the the flow of traffic from the internet, to traefik then to each namespace&#039;s gateway. Each gateway then looks at all connected certificates to add TLS authentication and then looks at the HTTPRoute to find which one matches based on the rules which then defines what service to forward the traffic to and subsequently the pods.]]&lt;br /&gt;
Kubernetes works by defining services, which give a common endpoint to call potentially multiple pods. These can then be exposed through HTTPRoutes and the [https://kubernetes.io/docs/concepts/services-networking/gateway/ Gateway API], in which traefik implements.&lt;br /&gt;
&lt;br /&gt;
The Gateway API resources are read by traefik, which acts as the implementation, and acts accordingly to the defined configuration. Therefore, in essence, the gateways act only as a means for configuring traefik. But effectively, traefik has configured open ports it can expose, it then looks for Gateways, in the permitted namespaces, for their configuration. The gateways stores a list of ports that the namespace can expose (though it cannot add one that is not included within traefik configuration itself), as well as a list of certificates. At this point, the domain requested must have a certificate configured within the gateway, and all TLS logic is handled by traefik and so all further traefik is effectively decrypted. Notice here that if a certificate is not configured on the gateway, it cannot be served (one of the downsides of the gateway API).&lt;br /&gt;
&lt;br /&gt;
For us, we have decided to have each namespace have their own gateway, due to the protections traefik offers, this means that we do not have to do any cross namespace references for certificates, and do not have to update the main gateway anytime we need to add a certificate. There is an additional issue with this, is that during the time the certificate doesn&#039;t exist but is configured (e.g. when first request it), the gateway is deemed invalid and so doesn&#039;t route any traefik (even http). There is a plan to help mitigate this through the use of &amp;lt;code&amp;gt;ListenerSets&amp;lt;/code&amp;gt; but this is yet to be supported in traefik and still has this issue. Therefore, we want to make sure that a single gateway hosts services for as few applications as possible (preferably only one).&lt;br /&gt;
&lt;br /&gt;
Anyway, the gateway will have a number of child routes (&amp;lt;code&amp;gt;TLSRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;HTTPRoute&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;GRPCRoutes&amp;lt;/code&amp;gt; and coming in the future &amp;lt;code&amp;gt;TCPRoute&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;UDPRoute&amp;lt;/code&amp;gt;). These routes act as queries to determine when and what traefik should forward to. So for example they act as:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
if hostname is example.bathcs.com forward to example-service&lt;br /&gt;
if the path starts with /api forward to api-service&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Then traefik can request those services, allowing kubernetes to effectively take over, looking at the pods associated with the service and using the defined algorithm to send the request to those pods and using the defined ports.&lt;br /&gt;
&lt;br /&gt;
This does mean there are a number of different places a port can change:&lt;br /&gt;
 Exposed port -&amp;gt; Traefik internal port -&amp;gt; Service port -&amp;gt; Pod port -&amp;gt; Application port&lt;br /&gt;
In most cases you should have the service port, pod port and application port all matching, this makes debugging a lot easier. Additionally there are few reasons why you will want to change traefik&#039;s internal port and the exposed port (but there are some!).&lt;br /&gt;
&lt;br /&gt;
==== Certificates ====&lt;br /&gt;
The thing with certificates is that we effectively never want to manually create them, the recommended expiry time for certificates is always dropping, with the most recent update at 45 days. This is way too much work for manual requesting and uploading and adds too many layers for it to go wrong. Therefore we use cert manager, which allows defining certificate objects within the cluster, cert manager will then go and do all the requesting for us and store it in a secret. Then it will also track the expiry and automatically update the certificate a week or so before it expires.&lt;br /&gt;
&lt;br /&gt;
There are multiple different methods it can use to validate that we are in fact in charge of the domain:&lt;br /&gt;
&lt;br /&gt;
* DNS - this is the preferred method, as it allows us generating certificates for protected IPs. But this requires a valid cloudflare API token (which is restricted to a single IP).&lt;br /&gt;
* HTTP - this is when the certificate authority will request our server from multiple locations, which means the DNS cannot be set to a protected IP. But it means we can generate certificates for domains that we don&#039;t control the DNS of (e.g. bath.ac.uk hostnames) but it is at least configured to point to our server. The integration with traefik means that there is no additional work required for the application to get these working.&lt;br /&gt;
* Cloudflare Origin - These are very special certificates and cannot be decrypted by the browser. The idea is that by generating these certificates, only cloudflare themselves will be able to decrypt the contents and so only they can proxy your IP. We use this specifically for [[Kubernetes#Cloudflare proxy|Cloudflare proxy]]-ing thought it doesn&#039;t provide us the true benefits (given our IP is still public)&lt;br /&gt;
&lt;br /&gt;
Within cert manager&#039;s speak, these are known as issuers, and we have cluster issuers defined for each (meaning any namespace in the cluster can use them).&lt;br /&gt;
{{Note|text=When cert manager is first requesting the certificate, the configured gateway will be invalid and so no routes attached will forward traffic.|type=reminder}}&lt;br /&gt;
&lt;br /&gt;
==== Cloudflare proxy ====&lt;br /&gt;
Cloudflare proxy offers the benefits of caching our content on &amp;quot;edge&amp;quot; servers, meaning that our websites perform much better on average as well as it can protect the IP of the machine, but as explained later we don&#039;t use cloudflare proxy everywhere and so lose this advantage. This caching is amazing when the application is configured for it to work well with it (e.g. correctly labelling requests as cachable). But it does not work with every application, especially third-party services which sometimes just break when using it. But it also adds troubling security questions, for example, a login page will also be proxied, and decrypted by cloudflare, resulting in cloudflare having access to all passwords that go through the site. For this reason we limit where we use cloudflare proxying to services that would benefit heavily from it (e.g. this Wiki as authentication is handled offsite).&lt;br /&gt;
&lt;br /&gt;
To setup cloudflare proxying, it is as simple as generating a certificate with the cloudflare origin issuer and exposing a HTTPRoute with the certificate and then enabling proxy in the dns record. Obviously this does not work with internal DNS records (e.g. &amp;lt;code&amp;gt;k8s.bathcs.com&amp;lt;/code&amp;gt;) and so our terraform config automatically detects and does not proxy this stuff.&lt;br /&gt;
&lt;br /&gt;
The cloudflare origin issuer then speaks to the cloudflare origin operator which requests a certificate from cloudflare themselves. The generated certificates can be found in the cloudflare dashboard for the domain under &amp;quot;SSL/TLS &amp;gt; Origin Server&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== Network Policies ===&lt;br /&gt;
&lt;br /&gt;
=== Helm ===&lt;br /&gt;
&lt;br /&gt;
=== Persistant Volumes ===&lt;br /&gt;
&lt;br /&gt;
=== Cronjobs ===&lt;br /&gt;
&lt;br /&gt;
=== K3S ===&lt;br /&gt;
&lt;br /&gt;
== Deployment with Tofu ==&lt;br /&gt;
&lt;br /&gt;
=== Retaining PVs ===&lt;br /&gt;
&lt;br /&gt;
=== Where is the state stored? ===&lt;br /&gt;
&lt;br /&gt;
== General Terraform management ==&lt;br /&gt;
&lt;br /&gt;
== K9s ==&lt;br /&gt;
&lt;br /&gt;
== Migrating storages ==&lt;br /&gt;
&lt;br /&gt;
== Multicluster setups ==&lt;br /&gt;
&lt;br /&gt;
=== Control plane ===&lt;br /&gt;
&lt;br /&gt;
=== How to manage Storage ===&lt;br /&gt;
&lt;br /&gt;
=== Safely shutting down nodes ===&lt;br /&gt;
&lt;br /&gt;
== Backups ==&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Kubernetes event log ===&lt;br /&gt;
&lt;br /&gt;
=== Traefik dashboard ===&lt;br /&gt;
&lt;br /&gt;
=== Shell-ing into pods ===&lt;br /&gt;
&lt;br /&gt;
=== K3S Log ===&lt;br /&gt;
&lt;br /&gt;
=== Emergency Debug pods ===&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare API tokens ===&lt;br /&gt;
&lt;br /&gt;
=== SELinux ===&lt;br /&gt;
&lt;br /&gt;
=== Scenarios ===&lt;br /&gt;
&lt;br /&gt;
==== Deployment/statefulset created but pod not creating ====&lt;br /&gt;
&lt;br /&gt;
==== Pod in cash loop ====&lt;br /&gt;
&lt;br /&gt;
==== Pod cannot request any website ====&lt;br /&gt;
&lt;br /&gt;
==== Pod can access the internet but everything returns self-signed certificate ====&lt;br /&gt;
&lt;br /&gt;
==== Deployment can&#039;t access its database/valkey ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is requesting denied system privileges ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 404 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 403 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Authelia refuses to start up ====&lt;br /&gt;
&lt;br /&gt;
==== Node is crashing often after running out of RAM ====&lt;br /&gt;
&lt;br /&gt;
==== On IPV6 cluster and requests randomly timeout or return 404 ====&lt;br /&gt;
&lt;br /&gt;
==== Statefulset refusing to start pod (PVC) ====&lt;br /&gt;
&lt;br /&gt;
==== A node just crashed and went offline ====&lt;br /&gt;
&lt;br /&gt;
=== Cleaning up ===&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=File:Gateway_diagram.svg&amp;diff=132</id>
		<title>File:Gateway diagram.svg</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=File:Gateway_diagram.svg&amp;diff=132"/>
		<updated>2026-06-04T13:34:56Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;A digram depicting the the flow of traffic from the internet, to traefik then to each namespace&#039;s gateway. Each gateway then looks at all connected certificates to add TLS authentication and then looks at the HTTPRoute to find which one matches based on the rules which then defines what service to forward the traffic to and subsequently the pods.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Terraform&amp;diff=131</id>
		<title>Terraform</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Terraform&amp;diff=131"/>
		<updated>2026-06-04T11:06:15Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Fix broken link&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[BOSS]] uses terraform (actually [https://opentofu.org/ &amp;lt;code&amp;gt;opentofu&amp;lt;/code&amp;gt;]) to deploy all the resources onto [[BOSS/Hosting/Cluster|their kubernetes cluster]], which tries to be the language in which you can deploy anything and everything and if you need to quickly redeploy a whole machine you can with a simple command. In practise it doesn’t work like that, but its good enough for our needs.&lt;br /&gt;
&lt;br /&gt;
You can find the [https://gitlab.bath.ac.uk/cs/int/terraform terraform repo on gitlab], this page was originally taken from the README as it got too long.&lt;br /&gt;
&lt;br /&gt;
== Other resources ==&lt;br /&gt;
&lt;br /&gt;
* [https://developer.hashicorp.com/terraform/tutorials Hashicorp (the maker of the closed source terraform) has some good tutorials on their site]&lt;br /&gt;
&lt;br /&gt;
Basically that’s it really, the rest of this will be looking at how to deploy an application with our configuration.&lt;br /&gt;
&lt;br /&gt;
== Terraform and its woes ==&lt;br /&gt;
&lt;br /&gt;
Before we get into making an app, I must briefly explain terraform and its benefits/issues/confusing behaviours.&lt;br /&gt;
&lt;br /&gt;
This expects you to have a rough idea around how terraform works. But here is a quick explainer: terraform is build around &#039;&#039;&#039;resources&#039;&#039;&#039;, provided by &#039;&#039;&#039;providers&#039;&#039;&#039;. These resources have a state stored locally in a state file, whether they are deployed, generated values etc. (note that these can literally be anything e.g. from random passwords to HTTP reequests to kubernetes resources to DNS records). These resources can then be organised into modules, which (can) have outputs from values generated by the resources. There are also “data”, but this is basically a reference to another resource which doesn’t have the controls.&lt;br /&gt;
&lt;br /&gt;
When deploying, terraform will then check the state of all the current deployed modules (even pinging servers if needed) and find anything that has changed (e.g. new resources or updated values) and deploy everything.&lt;br /&gt;
&lt;br /&gt;
=== Module structure ===&lt;br /&gt;
&lt;br /&gt;
Modules are the core of terraform and can be a bit tricky to get your head around, as initially they are quite limited (e.g. there is no such thing as a global variable).&lt;br /&gt;
&lt;br /&gt;
But basically a module has a list of inputs as and a list of outputs (and then providers). So it is expected that your module deploys some resources which are then used to output something. E.g. in [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/ldap/user|our ldap user module] the module generates a random password, creates the user and assigns them to a group, then outputs the username, email and password to be used later in another resource.&lt;br /&gt;
&lt;br /&gt;
So e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;user&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ldap/user&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  display   = &amp;quot;Example&amp;quot;&lt;br /&gt;
  username  = &amp;quot;example&amp;quot;&lt;br /&gt;
  group_ids = var.ldap_group_ids&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    // Note passing providers act somewhat like global constants, passing configuration (e.g. what ldap server we mean)&lt;br /&gt;
    // to the module&lt;br /&gt;
    lldap = lldap&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// You can then use the email by: module.user.email or password: module.user.password&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;: All files within a module (the folder) are treated as one global space, similar to how Go works. You can reference variables, locals and resources throughout all files within a module which makes it quite difficult to organise nicely.&lt;br /&gt;
&lt;br /&gt;
The modules are usually structure in the way:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;init.tf&amp;lt;/code&amp;gt; – Usually where your &amp;lt;code&amp;gt;providers&amp;lt;/code&amp;gt; go and if you are lazy (like me), everything else&lt;br /&gt;
* &amp;lt;code&amp;gt;vars.tf&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;variables.tf&amp;lt;/code&amp;gt; – Where you put all your variables. As explained in [[#Variable madness|Variable madness]], I really don’t like this and so commonly ignore&lt;br /&gt;
* &amp;lt;code&amp;gt;outputs.tf&amp;lt;/code&amp;gt; – All your outputs go here&lt;br /&gt;
* &amp;lt;code&amp;gt;*.tf&amp;lt;/code&amp;gt; – Anything else, if you want to split it out nicely into other files&lt;br /&gt;
&lt;br /&gt;
=== Variable madness ===&lt;br /&gt;
&lt;br /&gt;
Terraform variables suck.&lt;br /&gt;
&lt;br /&gt;
Anyway, so basically terraform requires you do a full definition for every variable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;variable &amp;quot;my_var&amp;quot; {&lt;br /&gt;
  type = string&lt;br /&gt;
&lt;br /&gt;
  description = &amp;quot;Something&amp;quot;&lt;br /&gt;
  nullable = false&lt;br /&gt;
&lt;br /&gt;
  sensitive = false # If something is sensitive MAKE THIS TRUE&lt;br /&gt;
}&lt;br /&gt;
// You can then later reference it with var.my_var&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This defines something that must be inputed by the user, either through the module or your &amp;lt;code&amp;gt;tfvars&amp;lt;/code&amp;gt; file (if in the root directory).&lt;br /&gt;
&lt;br /&gt;
Due to this verbosity, and sometimes complex nature of the interfaces I like to create, I have used the &amp;lt;code&amp;gt;object&amp;lt;/code&amp;gt; type e.g.:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;variable &amp;quot;my_var&amp;quot; {&lt;br /&gt;
  type = object({&lt;br /&gt;
    l = list(string)&lt;br /&gt;
    m = map(bool) # string -&amp;gt; bool. Same syntax as object, just more flexible&lt;br /&gt;
    s = set(string) # Yes this is different to list but using the same []&lt;br /&gt;
    option = optional(string, &amp;quot;my_default&amp;quot;)&lt;br /&gt;
  })&lt;br /&gt;
  // ...&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
But even then you can’t define nice defaults for each sub item and the description is for the whole variable, so it has to be done as so. This is just raw pain and not particularly great syntax in my opinion. Also you cannot enable/disable sensitive nature of values for sub items, this means the whole object must be defined as sensitive if you have one password.&lt;br /&gt;
&lt;br /&gt;
And due to the lack of global constants, you must define every variable in every sub project and duplicate the types (yes there is &#039;&#039;&#039;no&#039;&#039;&#039; way to define a type to use throughout the project).&lt;br /&gt;
&lt;br /&gt;
It is also recommended that you put all variables in a &amp;lt;code&amp;gt;vars.tf&amp;lt;/code&amp;gt; file. Which sure does make sense for small modules, but if its that small I find it easier to just chuck at the top of the &amp;lt;code&amp;gt;init.tf&amp;lt;/code&amp;gt; file (as the terraform syntax highlighter is soooo broken). Then if its large, I find it more useful to put the variables where they are actually used – but then again this is confusing because the syntax and tooling is so bad.&lt;br /&gt;
&lt;br /&gt;
Oh yeah sorry and then there are &#039;&#039;&#039;locals&#039;&#039;&#039; which are constants you can define from resources/variables and will be calculated when the information is ready. E.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;locals {&lt;br /&gt;
  temp_val = &amp;quot;hi&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// Then you can reference with local.temp_val&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Depends on and its pains ===&lt;br /&gt;
&lt;br /&gt;
The one issue with terraform is that its really slow with large projects with this. But the nature of the design encourages large projects (as you want to reference things throughout the smaller apps).&lt;br /&gt;
&lt;br /&gt;
This is due to it having to create a dependency graph where objects wait on their dependencies. These dependencies can be defined by &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt; in any resource or just referencing a value from another resource.&lt;br /&gt;
&lt;br /&gt;
This is really useful so deployments are not actually deployed until all the secrets are deployed. Due to my perferable of not repeating myself, I heavily use the inferred dependency from the referring to resource names e.g. &amp;lt;code&amp;gt;kubernetes_secret_v1.secret.metadata[0].name&amp;lt;/code&amp;gt; (yes this is why I don’t just do the simple thing and use the shorter name, its good to know where the value comes from).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT&#039;&#039;&#039; you cannot rely on this, as some things take time to actually deploy even if it says its successful. Therefore you may need to use timers instead. I usually don’t bother due to the numerous other issues with terraform making it so its not actually perfect so there’s minimal point actually making it easy to deploy from scratch.&lt;br /&gt;
&lt;br /&gt;
=== Timeouts ===&lt;br /&gt;
&lt;br /&gt;
If something goes wrong during deployment, e.g. you make a typo, you will have to wait the FULL timeout time. This is really painful when you typo the hostname to the db causing the pod to crashloop in a helm config and you have to wait 10 minutes for terraform to give up. You can Ctrl-C, Ctrl-C, but this causes more issues as you will have to manually intervene and delete the helm chart/deployment before you run the command again.&lt;br /&gt;
&lt;br /&gt;
Instead I recommend shortening the timeouts for the deployment/helm to one more applicable to the application. A lot of our first-party stuff usually deploys in a few seconds and if it doesn’t, something has gone very wrong.&lt;br /&gt;
&lt;br /&gt;
=== Commas or no commas? ===&lt;br /&gt;
&lt;br /&gt;
The terraform syntax is… interesting. Commas are optional in most cases. So I would recommend, not typing commas where they optional.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT&#039;&#039;&#039; within lists/sets (basically between &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt;) you have to type commas, and if this is across multiple lines &#039;&#039;&#039;PLEASE ADD TRAILING COMMAS&#039;&#039;&#039;. The reason? Git histories look sooooooo much better.&lt;br /&gt;
&lt;br /&gt;
== How to create a basic project ==&lt;br /&gt;
&lt;br /&gt;
=== Creating a namespace ===&lt;br /&gt;
&lt;br /&gt;
There is a handy util module for this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;example_namespace&amp;quot; {&lt;br /&gt;
  source = &amp;quot;./utils/namespace&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  name = &amp;quot;example&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  enable_dns = true&lt;br /&gt;
  enable_mail = true&lt;br /&gt;
  enable_lldap = true&lt;br /&gt;
  bkp = {&lt;br /&gt;
    // ...&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This allows you to enable or disable features for your namespace, e.g. if your pods need to communicate with the outside world, enabling the DNS. All of these features are disabled by default and its heavily encouraged to only enable the features if the namespace needs it.&lt;br /&gt;
&lt;br /&gt;
The next thing is to configure backups through this, to reduce our dependence and costs from our s3 provider, it is recommended that backups are disabled for all namespaces whos data can be regenerated (e.g. froom). If you do enable it, it is then encouraged that you disable backups for any database or pvc that you don’t need backing up with the &amp;lt;code&amp;gt;k8up.io/backup=false&amp;lt;/code&amp;gt; annotation (you may notice that all &amp;lt;code&amp;gt;valkey&amp;lt;/code&amp;gt; instances set this by default if you are using the &amp;lt;code&amp;gt;app/valkey&amp;lt;/code&amp;gt; module).&lt;br /&gt;
&lt;br /&gt;
==== Placement ====&lt;br /&gt;
&lt;br /&gt;
Within this repository, it is tradition to put the namespace creation at the highest level, e.g. &amp;lt;code&amp;gt;20_apps.tf&amp;lt;/code&amp;gt;. This means that the apps themselves do not control the namespace they are created in. It is mostly just a personal preference from me after years of configuring k8s on terraform.&lt;br /&gt;
&lt;br /&gt;
=== Using Helm ===&lt;br /&gt;
&lt;br /&gt;
Helm is by far the easiest way to deploy third-party tools, and is used throught this repo despite it’s drawbacks when combined with terraform (it’s just so easy).&lt;br /&gt;
&lt;br /&gt;
You just add helm to the providers list (which defines what terraform modules you are integrating with):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;terraform {&lt;br /&gt;
  required_providers {&lt;br /&gt;
    helm = {&lt;br /&gt;
      source  = &amp;quot;hashicorp/helm&amp;quot;&lt;br /&gt;
      version = &amp;quot;~&amp;gt;3.1.1&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then use the &amp;lt;code&amp;gt;helm_release&amp;lt;/code&amp;gt; resource, which takes the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;helm_release&amp;quot; &amp;quot;my_app&amp;quot; {&lt;br /&gt;
  name      = &amp;quot;my_app&amp;quot;&lt;br /&gt;
  namespace = var.namespace&lt;br /&gt;
&lt;br /&gt;
  repository = &amp;quot;https://charts.example.com&amp;quot;&lt;br /&gt;
  chart      = &amp;quot;the_app&amp;quot;&lt;br /&gt;
  version    = &amp;quot;version&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  values = [yamlencode({&lt;br /&gt;
    // Values go here written within the terraform config language&lt;br /&gt;
  })]&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This deploys all the resources an application needs and manages and restarting if a secret or config map changes, providing all the configuration at your fingertips.&lt;br /&gt;
&lt;br /&gt;
However this comes at a cost:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;SECRETS SHOULD NOT GO IN THE HELM CONFIG&#039;&#039;&#039;. This is a big one, all values are easily accessible unencrypted on the cluster, therefore any secrets &#039;&#039;&#039;MUST&#039;&#039;&#039; go in a &amp;lt;code&amp;gt;kubernetes_secrets_v1&amp;lt;/code&amp;gt; object and you should use a &amp;lt;code&amp;gt;secretsRef&amp;lt;/code&amp;gt; or similar to link it. If the helm chart does not support this &#039;&#039;&#039;DO NOT USE IT&#039;&#039;&#039;. Helm also stores the history of all values, therefore if you put it temporarily within helm values for testing, you must to a password rotation.&lt;br /&gt;
* You have no power over the types of resources and the structure in which it deploys. This means that if a feature or support for our strict network policies are not implemented, you have to either not use the helm chart completely or fork your own (which we definitely don’t want to do).&lt;br /&gt;
* If the helm chart gets deleted, all pvc related &#039;&#039;might&#039;&#039; also get deleted (unless they have the &amp;lt;code&amp;gt;Retain&amp;lt;/code&amp;gt; policy, which should be the case for everything).&lt;br /&gt;
* Sometimes they don’t have the proper security contexts/network policies by default so you will have to add them youself (see the below section)&lt;br /&gt;
&lt;br /&gt;
Overall, helm is pretty good, just use with caution and understand what templates you are inflicting. Note, you will probably have to get pretty good at reading not only default values, but schemas and the templating language of helm itself, as sometimes the charts are not particularly well documented.&lt;br /&gt;
&lt;br /&gt;
=== Using kubernetes ===&lt;br /&gt;
&lt;br /&gt;
For this you need to understand a bit of structure of how kubernetes works. I will assume that you are deploying a pod. If that pod needs storage attached (and not through SQL or Redis), then you will need to use a [https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ &amp;lt;code&amp;gt;StatefulSet&amp;lt;/code&amp;gt;]. If you have no storage or are just communicating with a postgres server or redis, you can instead use a [https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ &amp;lt;code&amp;gt;Deployment&amp;lt;/code&amp;gt;].&lt;br /&gt;
&lt;br /&gt;
The difference between these two concepts are not particularly seen in the world of a single node cluster, but basically deployments are free to spin up another even if the previous one is still terminating or if the node is non responsive. On the other hand, statefulsets must ensure that no two nodes are trying to access the same data, therefore cannot automatically start up if a node goes down.&lt;br /&gt;
&lt;br /&gt;
This also means that it is much easier to scale a deployment to multiple nodes, vs a statefulsets which must have separate volumes per pod.&lt;br /&gt;
&lt;br /&gt;
Anyway, both statefulsets and deployments have a template configuration for creating the pod associated with itself. This pod has a label which is used to monitor and track the associated pods with its parent. So the structure is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;my_deployment&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    replicas = 1&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = &amp;quot;the_deployment&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    // This doesn&#039;t really matter in a one node cluster with a replicas = 1&lt;br /&gt;
    strategy {&lt;br /&gt;
      type = &amp;quot;RollingUpdate&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = &amp;quot;the_deployment&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          name              = &amp;quot;my_deployment&amp;quot;&lt;br /&gt;
          image             = &amp;quot;bathbcss/my_image:latest&amp;quot;&lt;br /&gt;
          image_pull_policy = &amp;quot;Always&amp;quot; // Should only be set if the above is &amp;quot;latest&amp;quot;&lt;br /&gt;
&lt;br /&gt;
          // This should be the default security context to comply with our pod security policies&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // This is how the pod is checked its alive, so if something happens,&lt;br /&gt;
          // e.g. job which causes it to become unresponsive, it will be automatically killed off and replaced&lt;br /&gt;
          liveness_probe {&lt;br /&gt;
            http_get {&lt;br /&gt;
              path = &amp;quot;/healthz&amp;quot;&lt;br /&gt;
              port = 8080&lt;br /&gt;
            }&lt;br /&gt;
            initial_delay_seconds = 5&lt;br /&gt;
            period_seconds        = 10&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // This allows the tracking of when the pod starts, so we wait until the pod is ready to receive requests&lt;br /&gt;
          startup_probe {&lt;br /&gt;
            http_get {&lt;br /&gt;
              path = &amp;quot;/healthz&amp;quot;&lt;br /&gt;
              port = 8080&lt;br /&gt;
            }&lt;br /&gt;
            // 3 * 30 = 90 seconds to start&lt;br /&gt;
            failure_threshold = 30&lt;br /&gt;
            // If it takes a while to startup, increase this time&lt;br /&gt;
            period_seconds = 3&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // If exposing a port the port to expose&lt;br /&gt;
          port {&lt;br /&gt;
            container_port = 8080&lt;br /&gt;
            // Make sure to give it a name so we can use the name in services&lt;br /&gt;
            name           = &amp;quot;web&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // env and env_from definitions&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;Quick side note: &amp;lt;code&amp;gt;kubernetes_\*_v1&amp;lt;/code&amp;gt;is the preferred resouce name, any resource that does not have&amp;lt;code&amp;gt;\_v1&amp;lt;/code&amp;gt; on the end is deprecated and should not be used.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
It is recommended that the version of the image is actually set and &amp;lt;code&amp;gt;latest&amp;lt;/code&amp;gt; is not used, however to reduce the admin overhead, for internal projects it can be easier to set to &amp;lt;code&amp;gt;latest&amp;lt;/code&amp;gt; with an image pull policy of &amp;lt;code&amp;gt;Always&amp;lt;/code&amp;gt;. However this means if you want the latest version, you must have access to the cluster to restart a pod.&lt;br /&gt;
&lt;br /&gt;
If you are exposing pods, this should be tied with a &amp;lt;code&amp;gt;Service&amp;lt;/code&amp;gt;, as seen below.&lt;br /&gt;
&lt;br /&gt;
==== Security context ====&lt;br /&gt;
&lt;br /&gt;
As you will notice in the example above, we have a security context set. This is &#039;&#039;&#039;required&#039;&#039;&#039; by the pod security contenxt, otherwise it will not deploy. In most cases you can copy either of the two following policies, depending on whether it is within a kubernetes resource or helm/kubernetes manifest resource:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  security_context {&lt;br /&gt;
    run_as_user                = 1000&lt;br /&gt;
    run_as_non_root            = true&lt;br /&gt;
    allow_privilege_escalation = false&lt;br /&gt;
    seccomp_profile {&lt;br /&gt;
      type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    capabilities {&lt;br /&gt;
      drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    securityContext = {&lt;br /&gt;
      runAsUser                = 1000&lt;br /&gt;
      runAsNonRoot             = true&lt;br /&gt;
      allowPrivilegeEscalation = false&lt;br /&gt;
      seccompProfile = {&lt;br /&gt;
        type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      capabilities = {&lt;br /&gt;
        drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that we are running as a user (not root), setting the default seccompProfile (you &#039;&#039;should&#039;&#039; only need the default unless you are doing weird things with the host machine) as well as dropping all capabilities (you may need to add some back in but I will leave to you as you probably know more than me – NOTE: Some are disabled by our pod security policy but can be override with &amp;lt;code&amp;gt;baseline&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==== Liveness and startup probe ====&lt;br /&gt;
&lt;br /&gt;
The liveness and startup probes are not necessary, but is a nice to have. The liveness probe allows the cluster to detect if a pod becomes unresponsive and is then able to kill it if that is the case. Whereas a startup probe makes it so the cluster knows exactly when the pod is able to receive responses.&lt;br /&gt;
&lt;br /&gt;
Please see the [https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ kubernetes docs on probes] for more information on the options. But in most cases the HTTP get option should suffice, which just looks for a status 2xx code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  liveness_probe {&lt;br /&gt;
    http_get {&lt;br /&gt;
      port = 8080&lt;br /&gt;
    }&lt;br /&gt;
    initial_delay_seconds = 5&lt;br /&gt;
    period_seconds        = 10&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  startup_probe {&lt;br /&gt;
    http_get {&lt;br /&gt;
      port = 8080&lt;br /&gt;
    }&lt;br /&gt;
    failure_threshold = 30&lt;br /&gt;
    period_seconds = 3&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    livenessProbe = {&lt;br /&gt;
      httpGet = {&lt;br /&gt;
        port = 8080&lt;br /&gt;
      }&lt;br /&gt;
      initialDelaySeconds = 5&lt;br /&gt;
      periodSeconds       = 10&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    startupProbe = {&lt;br /&gt;
      httpGet = {&lt;br /&gt;
        port = 8080&lt;br /&gt;
      }&lt;br /&gt;
      failureThreshold = 30&lt;br /&gt;
      periodSeconds    = 3&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Environmental variables ====&lt;br /&gt;
&lt;br /&gt;
When configuring deployments, you will want to set environmental variables. There are a few ways to do it, but note &#039;&#039;&#039;ANY PASSWORDS/API KEYS GO IN SECRETS&#039;&#039;&#039; not the environmental variables. As you will see I will example how to do this.&lt;br /&gt;
&lt;br /&gt;
By default the &amp;lt;code&amp;gt;env&amp;lt;/code&amp;gt; list can be used to set a single environmental variable e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env {&lt;br /&gt;
    name = &amp;quot;TEST&amp;quot;&lt;br /&gt;
    value = &amp;quot;my_value&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, this is quite verbose and takes a lot of space, so if you are configuring a lot of variables or have secrets, you will want to use the &amp;lt;code&amp;gt;env_from&amp;lt;/code&amp;gt; list. This allows you to reference a config map or secrets (this is the most basic form).&lt;br /&gt;
&lt;br /&gt;
These look like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_secret_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;deployment-db&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  data = {&lt;br /&gt;
    DATABASE_URL = module.database.url&lt;br /&gt;
  }&lt;br /&gt;
  type = &amp;quot;Opaque&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_config_map_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;deployment-config&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  data = {&lt;br /&gt;
    PUBLIC_VALUE   = &amp;quot;yoooo&amp;quot;&lt;br /&gt;
    ROCKET_ADDRESS = &amp;quot;::&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env_from {&lt;br /&gt;
    secret_ref {&lt;br /&gt;
      name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  env_from {&lt;br /&gt;
    config_map_ref {&lt;br /&gt;
      name = kubernetes_config_map_v1.module.metadata[0].name&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    envFrom = [&lt;br /&gt;
      {&lt;br /&gt;
        secretRef = {&lt;br /&gt;
          name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
        configMapRef = {&lt;br /&gt;
          name = kubernetes_config_map_v1.module.metadata[0].name&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
    ]&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that you can set the value of a environmental variable from a secret on an individual basis, which can be useful if you are storing environmental variables as well as files inside your secret. E.g:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env {&lt;br /&gt;
    name = &amp;quot;DB_PASSWORD&amp;quot;&lt;br /&gt;
    value_from {&lt;br /&gt;
      secret_key_ref {&lt;br /&gt;
        name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
        key  = &amp;quot;password&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Third-party CRDs ====&lt;br /&gt;
&lt;br /&gt;
Now this is is where terraform becomes less good. Basically when deploying using the kubernetes API, the checks will make sure the CRDs (so the like api and kind are installed and supported on the kubernetes cluster). This means that you won’t even be able to run.&lt;br /&gt;
&lt;br /&gt;
Basically it means that you need to comment out manifests that reference these resources until the CRDs are deployed (usually though a helm chart or something).&lt;br /&gt;
&lt;br /&gt;
==== PVCs ====&lt;br /&gt;
&lt;br /&gt;
Please remember &#039;&#039;&#039;ANY PASSWORDS/API KEYS/CERTIFICATES GO IN SECRETS&#039;&#039;&#039; not in the storage (this also means its configurable by us and yes they can be mounted as read only volumes).&lt;br /&gt;
&lt;br /&gt;
So, kubernetes storage works around persistant volumes which are requested by persistant volume claims. On our k3s single node, we are just using the k3s filesystem class. This means it’s a bit basic but does the job.&lt;br /&gt;
&lt;br /&gt;
Things to note:&lt;br /&gt;
&lt;br /&gt;
* You probably should be manually creating persistant volume claims (and definitely not persistant volumes), instead using &amp;lt;code&amp;gt;statefulsets&amp;lt;/code&amp;gt;&lt;br /&gt;
* It’s really hard to change persistant volumes post fact, so please go through testing phase if you are unsure about anything.&lt;br /&gt;
* K3s does not support the storage limit, so please &#039;&#039;&#039;DON’T RELY ON IT&#039;&#039;&#039; to stop abusive behaviour.&lt;br /&gt;
* If you are defining yourself, do not accidentally make your deployment depend on the persistant volume claim, as the pvc will not be created until it is used in something. See [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/services/mail/mailserver.tf|the mailserver module] of how to handle it. &#039;&#039;&#039;NOTE&#039;&#039;&#039;: If you reference the pvc config in your deployment, terraform will add that automatically to the &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt; list.&lt;br /&gt;
* If it is critical data you will need to &#039;&#039;&#039;manually update the pv to “retain” its data&#039;&#039;&#039; if the pvc gets deleted. This just adds a bit of safety if you mess up a deployment. This can be done in &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; by an admin, updating the &amp;lt;code&amp;gt;persistentVolumeReclaimPolicy&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;Retain&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Delete&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
So how you should be using pvc, in statefulsets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_stateful_set_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    service_name = var.name&lt;br /&gt;
    replicas     = 1&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = var.name&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = var.name&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          name  = &amp;quot;my_app&amp;quot;&lt;br /&gt;
          image = &amp;quot;bathbcss/my_app:1.0.0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
          // ...&lt;br /&gt;
&lt;br /&gt;
          volume_mount {&lt;br /&gt;
            name       = &amp;quot;data&amp;quot;&lt;br /&gt;
            mount_path = &amp;quot;/data&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    volume_claim_template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        name = &amp;quot;data&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        access_modes = [&amp;quot;ReadWriteOnce&amp;quot;]&lt;br /&gt;
        resources {&lt;br /&gt;
          requests = {&lt;br /&gt;
            storage = &amp;quot;10Gi&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Services ====&lt;br /&gt;
&lt;br /&gt;
Servies are the first way to adding an ingress, and helps give a common endpoint if you have multiple pods running (though we don’t do this). If using within the cluster you can access a service with the address &amp;lt;code&amp;gt;service_name.namespace.svc.cluster.local&amp;lt;/code&amp;gt;. Note that if you are accessing it from within the same namespace you can just use the &amp;lt;code&amp;gt;service_name&amp;lt;/code&amp;gt; as the hostname (and it is better on a network policy basis).&lt;br /&gt;
&lt;br /&gt;
For a namespace you just need a label to select on (note we have to define an &amp;lt;code&amp;gt;app&amp;lt;/code&amp;gt; label for the statefulset/deployment anyway so you can just use this).&lt;br /&gt;
&lt;br /&gt;
So it should look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_service_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    port {&lt;br /&gt;
      port = 8080&lt;br /&gt;
      name = &amp;quot;web&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    selector = {&lt;br /&gt;
      app = var.name&lt;br /&gt;
    }&lt;br /&gt;
    type = &amp;quot;ClusterIP&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And then you can just access the port by &amp;lt;code&amp;gt;service_name:8080&amp;lt;/code&amp;gt;. Note that you can also set a &amp;lt;code&amp;gt;target_port&amp;lt;/code&amp;gt; if you want to change the port from the deployment and service (but like why?).&lt;br /&gt;
&lt;br /&gt;
Also giving it a &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; is important, so in the ingress we can just use the name instead of the port number itself, increasing readability.&lt;br /&gt;
&lt;br /&gt;
==== Network policies ====&lt;br /&gt;
&lt;br /&gt;
Now comes the pain. If a helm chart has a network policy, use that (but please actually read what permissions it gives).&lt;br /&gt;
&lt;br /&gt;
Network policies, either grant, or block network “ingress” (stuff going into the pod) and “egress” (stuff going out of the pod). By default, due to our security, ingress to our pods is denied (so anything in the cluster), but egress to the outside world is allowed.&lt;br /&gt;
&lt;br /&gt;
You then use either pod selectors or namespace selectors to allow traffic from a pod (which is how &amp;lt;code&amp;gt;enable_dns,lldap,mail&amp;lt;/code&amp;gt; works). In most cases egress can be left alone, unless you want to block a node from doing something in particular.&lt;br /&gt;
&lt;br /&gt;
As talked about in the next section, for postgres and valkey, we create these policies, limited to pods with the &amp;lt;code&amp;gt;${name}-${service}-client=true&amp;lt;/code&amp;gt; label allowing only inter-namespace communication.&lt;br /&gt;
&lt;br /&gt;
If you do have to write one, which I really hope you don’t, please read [https://kubernetes.io/docs/concepts/services-networking/network-policies/ kubernetes documentation on Network Policies].&lt;br /&gt;
&lt;br /&gt;
=== Postgres and Redis/Valkey ===&lt;br /&gt;
&lt;br /&gt;
We have utilities for [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/apps/postgres|postgres] and redis (through [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/apps/valkey|valkey] due to redis being really hard to run).&lt;br /&gt;
&lt;br /&gt;
If you require these, we highly recommend the above (though postgres needs moving away from bitnami due to the requirement of money for stability). These setup the network policies you need as well as generating secure passwords and you can look at the &amp;lt;code&amp;gt;outputs.tf&amp;lt;/code&amp;gt; file to see the outputs you can use from a module.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; If using this, for any pod accessing the data, it must be in the same namespace and have the labels &amp;lt;code&amp;gt;${name}-postgresql-client=true&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;${name}-valkey-client=true&amp;lt;/code&amp;gt;, otherwise the traffic will be denied by the network policies.&lt;br /&gt;
&lt;br /&gt;
E.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;database&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../postgres&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  namespace = var.namespace&lt;br /&gt;
  prefix    = var.name&lt;br /&gt;
  username  = &amp;quot;user&amp;quot;&lt;br /&gt;
  database  = &amp;quot;db_name&amp;quot;&lt;br /&gt;
  size      = &amp;quot;250Mi&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    helm = helm&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Create a secret with module.database.url&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = var.name&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = var.name&lt;br /&gt;
          // This **MUST** be defined otherwise you&#039;ll get weird errors&lt;br /&gt;
          froom-pg-postgresql-client = true&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // ...&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Side note: the namespace will also need to have &amp;lt;code&amp;gt;enable_dns&amp;lt;/code&amp;gt; to be able to access it&lt;br /&gt;
&lt;br /&gt;
=== Adding ingress ===&lt;br /&gt;
&lt;br /&gt;
Adding ingress to service basically means that you are making it accessible to the outside world.&lt;br /&gt;
&lt;br /&gt;
If you are doing this, please consider security heavily:&lt;br /&gt;
&lt;br /&gt;
* Can any user alter the DB? Are you doing proper type checking on the inputs?&lt;br /&gt;
* Do you make sure to not expose any secrets e.g. db urls&lt;br /&gt;
* Do you really need to expose this pod? Or can you leave it to k9s port forwarding?&lt;br /&gt;
&lt;br /&gt;
Then you need to ask:&lt;br /&gt;
&lt;br /&gt;
* Just expose it to people within the bath network e.g. on &amp;lt;code&amp;gt;*.k8s.bathcs.com&amp;lt;/code&amp;gt;&lt;br /&gt;
* Expose it to the whole world &amp;lt;code&amp;gt;*.bathcs.com&amp;lt;/code&amp;gt; (note coordination with backstage to get them to update their traefik will be necessary)&lt;br /&gt;
&lt;br /&gt;
Please expose to the utter minimum people.&lt;br /&gt;
&lt;br /&gt;
If exposing to the full network please note the flow is:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;app.bathcs.com&amp;lt;/code&amp;gt; &amp;amp;lt;- You should accept this domain in the ingress&lt;br /&gt;
* &amp;lt;code&amp;gt;app.bcss.su.bath.ac.uk&amp;lt;/code&amp;gt; &amp;amp;lt;- You should accept this domain in the ingress&lt;br /&gt;
* &amp;lt;code&amp;gt;app.k8s.bathcs.com&amp;lt;/code&amp;gt; &amp;amp;lt;- This is what the certificate you should be giving (due to this is hostname that backstage is requesting)&lt;br /&gt;
&lt;br /&gt;
Depending on the chosen level, please look at the [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/ingress|&amp;lt;code&amp;gt;utils/ingress/*&amp;lt;/code&amp;gt; modules] as these handle this most of this flow for you.&lt;br /&gt;
&lt;br /&gt;
E.g. making it internal to bath uni only:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;dns&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/dns_flow&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  subdomain = var.subdomain&lt;br /&gt;
  # We don&#039;t want public one, so don&#039;t specify cloudflare_zone_id&lt;br /&gt;
  k8s_cloudflare_zone_id = var.cloudflare_zone_id&lt;br /&gt;
&lt;br /&gt;
  # Expanded for effect&lt;br /&gt;
  domains = {&lt;br /&gt;
    uni = var.domains.uni&lt;br /&gt;
    k8s = var.domains.k8s&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    cloudflare = cloudflare,&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
module &amp;quot;ingress&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/tls&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  name                     = var.subdomain&lt;br /&gt;
  entry_points             = [&amp;quot;websecure&amp;quot;]&lt;br /&gt;
  namespace                = var.namespace&lt;br /&gt;
  host                     = module.dns.hosts.k8s&lt;br /&gt;
  additional_ingress_hosts = [module.dns.hosts.uni]&lt;br /&gt;
  service = {&lt;br /&gt;
    name = kubernetes_service_v1.module.metadata[0].name&lt;br /&gt;
    port = &amp;quot;http&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
  cert_issuer = var.cert_issuer&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes,&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If using a helm repo you may have to manually define the certificate, which is quite easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;cert&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    apiVersion = &amp;quot;cert-manager.io/v1&amp;quot;&lt;br /&gt;
    kind       = &amp;quot;Certificate&amp;quot;&lt;br /&gt;
    metadata = {&lt;br /&gt;
      name      = &amp;quot;${var.name}-cert&amp;quot;&lt;br /&gt;
      namespace = var.namespace&lt;br /&gt;
    }&lt;br /&gt;
    spec = {&lt;br /&gt;
      secretName = &amp;quot;${var.name}-cert-secret&amp;quot;&lt;br /&gt;
      issuerRef  = var.cert_issuer&lt;br /&gt;
      dnsNames   = [var.host]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which outputs the cert to the secret with name &amp;lt;code&amp;gt;${var.name}-cert-secret&amp;lt;/code&amp;gt;. You can also use &amp;lt;code&amp;gt;kubernetes_manifest.cert.manifest.spec.secretName&amp;lt;/code&amp;gt; (you can guess what I prefer).&lt;br /&gt;
&lt;br /&gt;
If you are making it publically accessible, you can use the full flow which does both dns and ingress records:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;ingress&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/full_flow&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  cloudflare_zone_id = var.cloudflare_zone_id&lt;br /&gt;
  namespace          = var.namespace&lt;br /&gt;
  domains            = var.domains&lt;br /&gt;
  subdomain          = var.subdomain&lt;br /&gt;
&lt;br /&gt;
  service = {&lt;br /&gt;
    name = kubernetes_service_v1.module.metadata[0].name&lt;br /&gt;
    port = &amp;quot;web&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
  cert_issuer = var.cert_issuer&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes,&lt;br /&gt;
    cloudflare = cloudflare&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Putting it behind authelia ====&lt;br /&gt;
&lt;br /&gt;
If the application doesn’t have oauth integration build in and you are wanting to protect it behind authelia you can add middleware of &amp;lt;code&amp;gt;auth-forwardauth-authelia@kubernetescrd&amp;lt;/code&amp;gt; to require them to go through authelia.&lt;br /&gt;
&lt;br /&gt;
You will then have to add an access control rule to authelia e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;rules = [&lt;br /&gt;
  {&lt;br /&gt;
    domain = &amp;amp;quot;app.k8s.bathcs.com&amp;amp;quot;&lt;br /&gt;
    subject = [&amp;amp;quot;group:boss-example-group&amp;amp;quot;, &amp;amp;quot;user:hw2210&amp;amp;quot;]&lt;br /&gt;
  },&lt;br /&gt;
]&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;: you can create and manage groups on [https://www.bath.ac.uk/groupmanager/ Bath&#039;s group manager], which creates a unix group that can be seen in the LDAP server.&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare dns records ===&lt;br /&gt;
&lt;br /&gt;
Cloudflare provider is an actual pain. You would’ve hoped it would be good, but its not amazing (has previously caused updates on every &amp;lt;code&amp;gt;apply&amp;lt;/code&amp;gt;). Basically when creating a dns record you should use the whole address e.g. &amp;lt;code&amp;gt;app.bathcs.com&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;app&amp;lt;/code&amp;gt;. This is because it will cause an update on the second application, changing the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; value (which we use to get the full domain).&lt;br /&gt;
&lt;br /&gt;
It sucks because theres an additional useless variable ontop of the &amp;lt;code&amp;gt;cloudflare_zone_id&amp;lt;/code&amp;gt; (yes you can use the “data” thing to solve this but still).&lt;br /&gt;
&lt;br /&gt;
Additionally for long TXT records, it will add additional quotes, and terraform will continually think you need to change that if you don’t yourself add the quotes into the data.&lt;br /&gt;
&lt;br /&gt;
=== Sending mail ===&lt;br /&gt;
&lt;br /&gt;
Sending mail is somewhat weird.&lt;br /&gt;
&lt;br /&gt;
Basically just because I can, the authentication is managed by &amp;lt;code&amp;gt;lldap&amp;lt;/code&amp;gt; (a really fast ldap implementation). This should not be confused with the ldap service that Authelia is hooked up with, as it is not.&lt;br /&gt;
&lt;br /&gt;
Once you’ve enabled mail in the namespace you then have to create an ldap user:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;user&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ldap/user&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  display   = &amp;quot;Example&amp;quot;&lt;br /&gt;
  username  = &amp;quot;example&amp;quot;&lt;br /&gt;
  group_ids = var.ldap_group_ids&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    lldap = lldap&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then use this to access the mail with &amp;lt;code&amp;gt;module.user.name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;module.user.password&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;module.user.email&amp;lt;/code&amp;gt;. &#039;&#039;&#039;NOTE&#039;&#039;&#039; due to it not being exposed, we must use the kubernetes domain certificate, which means that you normally have to disable tls verification it or set the expected domain to &amp;lt;code&amp;gt;bathcs.com&amp;lt;/code&amp;gt; (as seen in authelia).&lt;br /&gt;
&lt;br /&gt;
=== Creating an oauth client ===&lt;br /&gt;
&lt;br /&gt;
I have created a [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/oauth/client|OAuth client module] for generating all the secret data you need and generate the client config (under the &amp;lt;code&amp;gt;config&amp;lt;/code&amp;gt; output) you can then pass up and then into the authelia module (see &amp;lt;code&amp;gt;grafana&amp;lt;/code&amp;gt; as an example).&lt;br /&gt;
&lt;br /&gt;
But basically please read [https://www.authelia.com/integration/openid-connect/introduction/ the authelia oidc docs] for a full explanation of how it works.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Terraform&amp;diff=130</id>
		<title>Terraform</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Terraform&amp;diff=130"/>
		<updated>2026-06-04T11:05:19Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: /* Putting it behind authelia */ Update information about using bath&amp;#039;s group manager for authentication&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[BOSS]] uses terraform (actually [https://opentofu.org/ &amp;lt;code&amp;gt;opentofu&amp;lt;/code&amp;gt;]) to deploy all the resources onto [[BOSS/Hosting/Cluster their kubernetes cluster]], which tries to be the language in which you can deploy anything and everything and if you need to quickly redeploy a whole machine you can with a simple command. In practise it doesn’t work like that, but its good enough for our needs.&lt;br /&gt;
&lt;br /&gt;
You can find the [https://gitlab.bath.ac.uk/cs/int/terraform terraform repo on gitlab], this page was originally taken from the README as it got too long.&lt;br /&gt;
&lt;br /&gt;
== Other resources ==&lt;br /&gt;
&lt;br /&gt;
* [https://developer.hashicorp.com/terraform/tutorials Hashicorp (the maker of the closed source terraform) has some good tutorials on their site]&lt;br /&gt;
&lt;br /&gt;
Basically that’s it really, the rest of this will be looking at how to deploy an application with our configuration.&lt;br /&gt;
&lt;br /&gt;
== Terraform and its woes ==&lt;br /&gt;
&lt;br /&gt;
Before we get into making an app, I must briefly explain terraform and its benefits/issues/confusing behaviours.&lt;br /&gt;
&lt;br /&gt;
This expects you to have a rough idea around how terraform works. But here is a quick explainer: terraform is build around &#039;&#039;&#039;resources&#039;&#039;&#039;, provided by &#039;&#039;&#039;providers&#039;&#039;&#039;. These resources have a state stored locally in a state file, whether they are deployed, generated values etc. (note that these can literally be anything e.g. from random passwords to HTTP reequests to kubernetes resources to DNS records). These resources can then be organised into modules, which (can) have outputs from values generated by the resources. There are also “data”, but this is basically a reference to another resource which doesn’t have the controls.&lt;br /&gt;
&lt;br /&gt;
When deploying, terraform will then check the state of all the current deployed modules (even pinging servers if needed) and find anything that has changed (e.g. new resources or updated values) and deploy everything.&lt;br /&gt;
&lt;br /&gt;
=== Module structure ===&lt;br /&gt;
&lt;br /&gt;
Modules are the core of terraform and can be a bit tricky to get your head around, as initially they are quite limited (e.g. there is no such thing as a global variable).&lt;br /&gt;
&lt;br /&gt;
But basically a module has a list of inputs as and a list of outputs (and then providers). So it is expected that your module deploys some resources which are then used to output something. E.g. in [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/ldap/user|our ldap user module] the module generates a random password, creates the user and assigns them to a group, then outputs the username, email and password to be used later in another resource.&lt;br /&gt;
&lt;br /&gt;
So e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;user&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ldap/user&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  display   = &amp;quot;Example&amp;quot;&lt;br /&gt;
  username  = &amp;quot;example&amp;quot;&lt;br /&gt;
  group_ids = var.ldap_group_ids&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    // Note passing providers act somewhat like global constants, passing configuration (e.g. what ldap server we mean)&lt;br /&gt;
    // to the module&lt;br /&gt;
    lldap = lldap&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// You can then use the email by: module.user.email or password: module.user.password&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;: All files within a module (the folder) are treated as one global space, similar to how Go works. You can reference variables, locals and resources throughout all files within a module which makes it quite difficult to organise nicely.&lt;br /&gt;
&lt;br /&gt;
The modules are usually structure in the way:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;init.tf&amp;lt;/code&amp;gt; – Usually where your &amp;lt;code&amp;gt;providers&amp;lt;/code&amp;gt; go and if you are lazy (like me), everything else&lt;br /&gt;
* &amp;lt;code&amp;gt;vars.tf&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;variables.tf&amp;lt;/code&amp;gt; – Where you put all your variables. As explained in [[#Variable madness|Variable madness]], I really don’t like this and so commonly ignore&lt;br /&gt;
* &amp;lt;code&amp;gt;outputs.tf&amp;lt;/code&amp;gt; – All your outputs go here&lt;br /&gt;
* &amp;lt;code&amp;gt;*.tf&amp;lt;/code&amp;gt; – Anything else, if you want to split it out nicely into other files&lt;br /&gt;
&lt;br /&gt;
=== Variable madness ===&lt;br /&gt;
&lt;br /&gt;
Terraform variables suck.&lt;br /&gt;
&lt;br /&gt;
Anyway, so basically terraform requires you do a full definition for every variable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;variable &amp;quot;my_var&amp;quot; {&lt;br /&gt;
  type = string&lt;br /&gt;
&lt;br /&gt;
  description = &amp;quot;Something&amp;quot;&lt;br /&gt;
  nullable = false&lt;br /&gt;
&lt;br /&gt;
  sensitive = false # If something is sensitive MAKE THIS TRUE&lt;br /&gt;
}&lt;br /&gt;
// You can then later reference it with var.my_var&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This defines something that must be inputed by the user, either through the module or your &amp;lt;code&amp;gt;tfvars&amp;lt;/code&amp;gt; file (if in the root directory).&lt;br /&gt;
&lt;br /&gt;
Due to this verbosity, and sometimes complex nature of the interfaces I like to create, I have used the &amp;lt;code&amp;gt;object&amp;lt;/code&amp;gt; type e.g.:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;variable &amp;quot;my_var&amp;quot; {&lt;br /&gt;
  type = object({&lt;br /&gt;
    l = list(string)&lt;br /&gt;
    m = map(bool) # string -&amp;gt; bool. Same syntax as object, just more flexible&lt;br /&gt;
    s = set(string) # Yes this is different to list but using the same []&lt;br /&gt;
    option = optional(string, &amp;quot;my_default&amp;quot;)&lt;br /&gt;
  })&lt;br /&gt;
  // ...&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
But even then you can’t define nice defaults for each sub item and the description is for the whole variable, so it has to be done as so. This is just raw pain and not particularly great syntax in my opinion. Also you cannot enable/disable sensitive nature of values for sub items, this means the whole object must be defined as sensitive if you have one password.&lt;br /&gt;
&lt;br /&gt;
And due to the lack of global constants, you must define every variable in every sub project and duplicate the types (yes there is &#039;&#039;&#039;no&#039;&#039;&#039; way to define a type to use throughout the project).&lt;br /&gt;
&lt;br /&gt;
It is also recommended that you put all variables in a &amp;lt;code&amp;gt;vars.tf&amp;lt;/code&amp;gt; file. Which sure does make sense for small modules, but if its that small I find it easier to just chuck at the top of the &amp;lt;code&amp;gt;init.tf&amp;lt;/code&amp;gt; file (as the terraform syntax highlighter is soooo broken). Then if its large, I find it more useful to put the variables where they are actually used – but then again this is confusing because the syntax and tooling is so bad.&lt;br /&gt;
&lt;br /&gt;
Oh yeah sorry and then there are &#039;&#039;&#039;locals&#039;&#039;&#039; which are constants you can define from resources/variables and will be calculated when the information is ready. E.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;locals {&lt;br /&gt;
  temp_val = &amp;quot;hi&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// Then you can reference with local.temp_val&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Depends on and its pains ===&lt;br /&gt;
&lt;br /&gt;
The one issue with terraform is that its really slow with large projects with this. But the nature of the design encourages large projects (as you want to reference things throughout the smaller apps).&lt;br /&gt;
&lt;br /&gt;
This is due to it having to create a dependency graph where objects wait on their dependencies. These dependencies can be defined by &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt; in any resource or just referencing a value from another resource.&lt;br /&gt;
&lt;br /&gt;
This is really useful so deployments are not actually deployed until all the secrets are deployed. Due to my perferable of not repeating myself, I heavily use the inferred dependency from the referring to resource names e.g. &amp;lt;code&amp;gt;kubernetes_secret_v1.secret.metadata[0].name&amp;lt;/code&amp;gt; (yes this is why I don’t just do the simple thing and use the shorter name, its good to know where the value comes from).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT&#039;&#039;&#039; you cannot rely on this, as some things take time to actually deploy even if it says its successful. Therefore you may need to use timers instead. I usually don’t bother due to the numerous other issues with terraform making it so its not actually perfect so there’s minimal point actually making it easy to deploy from scratch.&lt;br /&gt;
&lt;br /&gt;
=== Timeouts ===&lt;br /&gt;
&lt;br /&gt;
If something goes wrong during deployment, e.g. you make a typo, you will have to wait the FULL timeout time. This is really painful when you typo the hostname to the db causing the pod to crashloop in a helm config and you have to wait 10 minutes for terraform to give up. You can Ctrl-C, Ctrl-C, but this causes more issues as you will have to manually intervene and delete the helm chart/deployment before you run the command again.&lt;br /&gt;
&lt;br /&gt;
Instead I recommend shortening the timeouts for the deployment/helm to one more applicable to the application. A lot of our first-party stuff usually deploys in a few seconds and if it doesn’t, something has gone very wrong.&lt;br /&gt;
&lt;br /&gt;
=== Commas or no commas? ===&lt;br /&gt;
&lt;br /&gt;
The terraform syntax is… interesting. Commas are optional in most cases. So I would recommend, not typing commas where they optional.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT&#039;&#039;&#039; within lists/sets (basically between &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt;) you have to type commas, and if this is across multiple lines &#039;&#039;&#039;PLEASE ADD TRAILING COMMAS&#039;&#039;&#039;. The reason? Git histories look sooooooo much better.&lt;br /&gt;
&lt;br /&gt;
== How to create a basic project ==&lt;br /&gt;
&lt;br /&gt;
=== Creating a namespace ===&lt;br /&gt;
&lt;br /&gt;
There is a handy util module for this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;example_namespace&amp;quot; {&lt;br /&gt;
  source = &amp;quot;./utils/namespace&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  name = &amp;quot;example&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  enable_dns = true&lt;br /&gt;
  enable_mail = true&lt;br /&gt;
  enable_lldap = true&lt;br /&gt;
  bkp = {&lt;br /&gt;
    // ...&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This allows you to enable or disable features for your namespace, e.g. if your pods need to communicate with the outside world, enabling the DNS. All of these features are disabled by default and its heavily encouraged to only enable the features if the namespace needs it.&lt;br /&gt;
&lt;br /&gt;
The next thing is to configure backups through this, to reduce our dependence and costs from our s3 provider, it is recommended that backups are disabled for all namespaces whos data can be regenerated (e.g. froom). If you do enable it, it is then encouraged that you disable backups for any database or pvc that you don’t need backing up with the &amp;lt;code&amp;gt;k8up.io/backup=false&amp;lt;/code&amp;gt; annotation (you may notice that all &amp;lt;code&amp;gt;valkey&amp;lt;/code&amp;gt; instances set this by default if you are using the &amp;lt;code&amp;gt;app/valkey&amp;lt;/code&amp;gt; module).&lt;br /&gt;
&lt;br /&gt;
==== Placement ====&lt;br /&gt;
&lt;br /&gt;
Within this repository, it is tradition to put the namespace creation at the highest level, e.g. &amp;lt;code&amp;gt;20_apps.tf&amp;lt;/code&amp;gt;. This means that the apps themselves do not control the namespace they are created in. It is mostly just a personal preference from me after years of configuring k8s on terraform.&lt;br /&gt;
&lt;br /&gt;
=== Using Helm ===&lt;br /&gt;
&lt;br /&gt;
Helm is by far the easiest way to deploy third-party tools, and is used throught this repo despite it’s drawbacks when combined with terraform (it’s just so easy).&lt;br /&gt;
&lt;br /&gt;
You just add helm to the providers list (which defines what terraform modules you are integrating with):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;terraform {&lt;br /&gt;
  required_providers {&lt;br /&gt;
    helm = {&lt;br /&gt;
      source  = &amp;quot;hashicorp/helm&amp;quot;&lt;br /&gt;
      version = &amp;quot;~&amp;gt;3.1.1&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then use the &amp;lt;code&amp;gt;helm_release&amp;lt;/code&amp;gt; resource, which takes the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;helm_release&amp;quot; &amp;quot;my_app&amp;quot; {&lt;br /&gt;
  name      = &amp;quot;my_app&amp;quot;&lt;br /&gt;
  namespace = var.namespace&lt;br /&gt;
&lt;br /&gt;
  repository = &amp;quot;https://charts.example.com&amp;quot;&lt;br /&gt;
  chart      = &amp;quot;the_app&amp;quot;&lt;br /&gt;
  version    = &amp;quot;version&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  values = [yamlencode({&lt;br /&gt;
    // Values go here written within the terraform config language&lt;br /&gt;
  })]&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This deploys all the resources an application needs and manages and restarting if a secret or config map changes, providing all the configuration at your fingertips.&lt;br /&gt;
&lt;br /&gt;
However this comes at a cost:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;SECRETS SHOULD NOT GO IN THE HELM CONFIG&#039;&#039;&#039;. This is a big one, all values are easily accessible unencrypted on the cluster, therefore any secrets &#039;&#039;&#039;MUST&#039;&#039;&#039; go in a &amp;lt;code&amp;gt;kubernetes_secrets_v1&amp;lt;/code&amp;gt; object and you should use a &amp;lt;code&amp;gt;secretsRef&amp;lt;/code&amp;gt; or similar to link it. If the helm chart does not support this &#039;&#039;&#039;DO NOT USE IT&#039;&#039;&#039;. Helm also stores the history of all values, therefore if you put it temporarily within helm values for testing, you must to a password rotation.&lt;br /&gt;
* You have no power over the types of resources and the structure in which it deploys. This means that if a feature or support for our strict network policies are not implemented, you have to either not use the helm chart completely or fork your own (which we definitely don’t want to do).&lt;br /&gt;
* If the helm chart gets deleted, all pvc related &#039;&#039;might&#039;&#039; also get deleted (unless they have the &amp;lt;code&amp;gt;Retain&amp;lt;/code&amp;gt; policy, which should be the case for everything).&lt;br /&gt;
* Sometimes they don’t have the proper security contexts/network policies by default so you will have to add them youself (see the below section)&lt;br /&gt;
&lt;br /&gt;
Overall, helm is pretty good, just use with caution and understand what templates you are inflicting. Note, you will probably have to get pretty good at reading not only default values, but schemas and the templating language of helm itself, as sometimes the charts are not particularly well documented.&lt;br /&gt;
&lt;br /&gt;
=== Using kubernetes ===&lt;br /&gt;
&lt;br /&gt;
For this you need to understand a bit of structure of how kubernetes works. I will assume that you are deploying a pod. If that pod needs storage attached (and not through SQL or Redis), then you will need to use a [https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ &amp;lt;code&amp;gt;StatefulSet&amp;lt;/code&amp;gt;]. If you have no storage or are just communicating with a postgres server or redis, you can instead use a [https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ &amp;lt;code&amp;gt;Deployment&amp;lt;/code&amp;gt;].&lt;br /&gt;
&lt;br /&gt;
The difference between these two concepts are not particularly seen in the world of a single node cluster, but basically deployments are free to spin up another even if the previous one is still terminating or if the node is non responsive. On the other hand, statefulsets must ensure that no two nodes are trying to access the same data, therefore cannot automatically start up if a node goes down.&lt;br /&gt;
&lt;br /&gt;
This also means that it is much easier to scale a deployment to multiple nodes, vs a statefulsets which must have separate volumes per pod.&lt;br /&gt;
&lt;br /&gt;
Anyway, both statefulsets and deployments have a template configuration for creating the pod associated with itself. This pod has a label which is used to monitor and track the associated pods with its parent. So the structure is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;my_deployment&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    replicas = 1&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = &amp;quot;the_deployment&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    // This doesn&#039;t really matter in a one node cluster with a replicas = 1&lt;br /&gt;
    strategy {&lt;br /&gt;
      type = &amp;quot;RollingUpdate&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = &amp;quot;the_deployment&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          name              = &amp;quot;my_deployment&amp;quot;&lt;br /&gt;
          image             = &amp;quot;bathbcss/my_image:latest&amp;quot;&lt;br /&gt;
          image_pull_policy = &amp;quot;Always&amp;quot; // Should only be set if the above is &amp;quot;latest&amp;quot;&lt;br /&gt;
&lt;br /&gt;
          // This should be the default security context to comply with our pod security policies&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // This is how the pod is checked its alive, so if something happens,&lt;br /&gt;
          // e.g. job which causes it to become unresponsive, it will be automatically killed off and replaced&lt;br /&gt;
          liveness_probe {&lt;br /&gt;
            http_get {&lt;br /&gt;
              path = &amp;quot;/healthz&amp;quot;&lt;br /&gt;
              port = 8080&lt;br /&gt;
            }&lt;br /&gt;
            initial_delay_seconds = 5&lt;br /&gt;
            period_seconds        = 10&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // This allows the tracking of when the pod starts, so we wait until the pod is ready to receive requests&lt;br /&gt;
          startup_probe {&lt;br /&gt;
            http_get {&lt;br /&gt;
              path = &amp;quot;/healthz&amp;quot;&lt;br /&gt;
              port = 8080&lt;br /&gt;
            }&lt;br /&gt;
            // 3 * 30 = 90 seconds to start&lt;br /&gt;
            failure_threshold = 30&lt;br /&gt;
            // If it takes a while to startup, increase this time&lt;br /&gt;
            period_seconds = 3&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // If exposing a port the port to expose&lt;br /&gt;
          port {&lt;br /&gt;
            container_port = 8080&lt;br /&gt;
            // Make sure to give it a name so we can use the name in services&lt;br /&gt;
            name           = &amp;quot;web&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // env and env_from definitions&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;Quick side note: &amp;lt;code&amp;gt;kubernetes_\*_v1&amp;lt;/code&amp;gt;is the preferred resouce name, any resource that does not have&amp;lt;code&amp;gt;\_v1&amp;lt;/code&amp;gt; on the end is deprecated and should not be used.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
It is recommended that the version of the image is actually set and &amp;lt;code&amp;gt;latest&amp;lt;/code&amp;gt; is not used, however to reduce the admin overhead, for internal projects it can be easier to set to &amp;lt;code&amp;gt;latest&amp;lt;/code&amp;gt; with an image pull policy of &amp;lt;code&amp;gt;Always&amp;lt;/code&amp;gt;. However this means if you want the latest version, you must have access to the cluster to restart a pod.&lt;br /&gt;
&lt;br /&gt;
If you are exposing pods, this should be tied with a &amp;lt;code&amp;gt;Service&amp;lt;/code&amp;gt;, as seen below.&lt;br /&gt;
&lt;br /&gt;
==== Security context ====&lt;br /&gt;
&lt;br /&gt;
As you will notice in the example above, we have a security context set. This is &#039;&#039;&#039;required&#039;&#039;&#039; by the pod security contenxt, otherwise it will not deploy. In most cases you can copy either of the two following policies, depending on whether it is within a kubernetes resource or helm/kubernetes manifest resource:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  security_context {&lt;br /&gt;
    run_as_user                = 1000&lt;br /&gt;
    run_as_non_root            = true&lt;br /&gt;
    allow_privilege_escalation = false&lt;br /&gt;
    seccomp_profile {&lt;br /&gt;
      type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    capabilities {&lt;br /&gt;
      drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    securityContext = {&lt;br /&gt;
      runAsUser                = 1000&lt;br /&gt;
      runAsNonRoot             = true&lt;br /&gt;
      allowPrivilegeEscalation = false&lt;br /&gt;
      seccompProfile = {&lt;br /&gt;
        type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      capabilities = {&lt;br /&gt;
        drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that we are running as a user (not root), setting the default seccompProfile (you &#039;&#039;should&#039;&#039; only need the default unless you are doing weird things with the host machine) as well as dropping all capabilities (you may need to add some back in but I will leave to you as you probably know more than me – NOTE: Some are disabled by our pod security policy but can be override with &amp;lt;code&amp;gt;baseline&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==== Liveness and startup probe ====&lt;br /&gt;
&lt;br /&gt;
The liveness and startup probes are not necessary, but is a nice to have. The liveness probe allows the cluster to detect if a pod becomes unresponsive and is then able to kill it if that is the case. Whereas a startup probe makes it so the cluster knows exactly when the pod is able to receive responses.&lt;br /&gt;
&lt;br /&gt;
Please see the [https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ kubernetes docs on probes] for more information on the options. But in most cases the HTTP get option should suffice, which just looks for a status 2xx code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  liveness_probe {&lt;br /&gt;
    http_get {&lt;br /&gt;
      port = 8080&lt;br /&gt;
    }&lt;br /&gt;
    initial_delay_seconds = 5&lt;br /&gt;
    period_seconds        = 10&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  startup_probe {&lt;br /&gt;
    http_get {&lt;br /&gt;
      port = 8080&lt;br /&gt;
    }&lt;br /&gt;
    failure_threshold = 30&lt;br /&gt;
    period_seconds = 3&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    livenessProbe = {&lt;br /&gt;
      httpGet = {&lt;br /&gt;
        port = 8080&lt;br /&gt;
      }&lt;br /&gt;
      initialDelaySeconds = 5&lt;br /&gt;
      periodSeconds       = 10&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    startupProbe = {&lt;br /&gt;
      httpGet = {&lt;br /&gt;
        port = 8080&lt;br /&gt;
      }&lt;br /&gt;
      failureThreshold = 30&lt;br /&gt;
      periodSeconds    = 3&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Environmental variables ====&lt;br /&gt;
&lt;br /&gt;
When configuring deployments, you will want to set environmental variables. There are a few ways to do it, but note &#039;&#039;&#039;ANY PASSWORDS/API KEYS GO IN SECRETS&#039;&#039;&#039; not the environmental variables. As you will see I will example how to do this.&lt;br /&gt;
&lt;br /&gt;
By default the &amp;lt;code&amp;gt;env&amp;lt;/code&amp;gt; list can be used to set a single environmental variable e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env {&lt;br /&gt;
    name = &amp;quot;TEST&amp;quot;&lt;br /&gt;
    value = &amp;quot;my_value&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, this is quite verbose and takes a lot of space, so if you are configuring a lot of variables or have secrets, you will want to use the &amp;lt;code&amp;gt;env_from&amp;lt;/code&amp;gt; list. This allows you to reference a config map or secrets (this is the most basic form).&lt;br /&gt;
&lt;br /&gt;
These look like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_secret_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;deployment-db&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  data = {&lt;br /&gt;
    DATABASE_URL = module.database.url&lt;br /&gt;
  }&lt;br /&gt;
  type = &amp;quot;Opaque&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_config_map_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;deployment-config&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  data = {&lt;br /&gt;
    PUBLIC_VALUE   = &amp;quot;yoooo&amp;quot;&lt;br /&gt;
    ROCKET_ADDRESS = &amp;quot;::&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env_from {&lt;br /&gt;
    secret_ref {&lt;br /&gt;
      name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  env_from {&lt;br /&gt;
    config_map_ref {&lt;br /&gt;
      name = kubernetes_config_map_v1.module.metadata[0].name&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    envFrom = [&lt;br /&gt;
      {&lt;br /&gt;
        secretRef = {&lt;br /&gt;
          name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
        configMapRef = {&lt;br /&gt;
          name = kubernetes_config_map_v1.module.metadata[0].name&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
    ]&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that you can set the value of a environmental variable from a secret on an individual basis, which can be useful if you are storing environmental variables as well as files inside your secret. E.g:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env {&lt;br /&gt;
    name = &amp;quot;DB_PASSWORD&amp;quot;&lt;br /&gt;
    value_from {&lt;br /&gt;
      secret_key_ref {&lt;br /&gt;
        name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
        key  = &amp;quot;password&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Third-party CRDs ====&lt;br /&gt;
&lt;br /&gt;
Now this is is where terraform becomes less good. Basically when deploying using the kubernetes API, the checks will make sure the CRDs (so the like api and kind are installed and supported on the kubernetes cluster). This means that you won’t even be able to run.&lt;br /&gt;
&lt;br /&gt;
Basically it means that you need to comment out manifests that reference these resources until the CRDs are deployed (usually though a helm chart or something).&lt;br /&gt;
&lt;br /&gt;
==== PVCs ====&lt;br /&gt;
&lt;br /&gt;
Please remember &#039;&#039;&#039;ANY PASSWORDS/API KEYS/CERTIFICATES GO IN SECRETS&#039;&#039;&#039; not in the storage (this also means its configurable by us and yes they can be mounted as read only volumes).&lt;br /&gt;
&lt;br /&gt;
So, kubernetes storage works around persistant volumes which are requested by persistant volume claims. On our k3s single node, we are just using the k3s filesystem class. This means it’s a bit basic but does the job.&lt;br /&gt;
&lt;br /&gt;
Things to note:&lt;br /&gt;
&lt;br /&gt;
* You probably should be manually creating persistant volume claims (and definitely not persistant volumes), instead using &amp;lt;code&amp;gt;statefulsets&amp;lt;/code&amp;gt;&lt;br /&gt;
* It’s really hard to change persistant volumes post fact, so please go through testing phase if you are unsure about anything.&lt;br /&gt;
* K3s does not support the storage limit, so please &#039;&#039;&#039;DON’T RELY ON IT&#039;&#039;&#039; to stop abusive behaviour.&lt;br /&gt;
* If you are defining yourself, do not accidentally make your deployment depend on the persistant volume claim, as the pvc will not be created until it is used in something. See [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/services/mail/mailserver.tf|the mailserver module] of how to handle it. &#039;&#039;&#039;NOTE&#039;&#039;&#039;: If you reference the pvc config in your deployment, terraform will add that automatically to the &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt; list.&lt;br /&gt;
* If it is critical data you will need to &#039;&#039;&#039;manually update the pv to “retain” its data&#039;&#039;&#039; if the pvc gets deleted. This just adds a bit of safety if you mess up a deployment. This can be done in &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; by an admin, updating the &amp;lt;code&amp;gt;persistentVolumeReclaimPolicy&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;Retain&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Delete&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
So how you should be using pvc, in statefulsets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_stateful_set_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    service_name = var.name&lt;br /&gt;
    replicas     = 1&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = var.name&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = var.name&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          name  = &amp;quot;my_app&amp;quot;&lt;br /&gt;
          image = &amp;quot;bathbcss/my_app:1.0.0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
          // ...&lt;br /&gt;
&lt;br /&gt;
          volume_mount {&lt;br /&gt;
            name       = &amp;quot;data&amp;quot;&lt;br /&gt;
            mount_path = &amp;quot;/data&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    volume_claim_template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        name = &amp;quot;data&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        access_modes = [&amp;quot;ReadWriteOnce&amp;quot;]&lt;br /&gt;
        resources {&lt;br /&gt;
          requests = {&lt;br /&gt;
            storage = &amp;quot;10Gi&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Services ====&lt;br /&gt;
&lt;br /&gt;
Servies are the first way to adding an ingress, and helps give a common endpoint if you have multiple pods running (though we don’t do this). If using within the cluster you can access a service with the address &amp;lt;code&amp;gt;service_name.namespace.svc.cluster.local&amp;lt;/code&amp;gt;. Note that if you are accessing it from within the same namespace you can just use the &amp;lt;code&amp;gt;service_name&amp;lt;/code&amp;gt; as the hostname (and it is better on a network policy basis).&lt;br /&gt;
&lt;br /&gt;
For a namespace you just need a label to select on (note we have to define an &amp;lt;code&amp;gt;app&amp;lt;/code&amp;gt; label for the statefulset/deployment anyway so you can just use this).&lt;br /&gt;
&lt;br /&gt;
So it should look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_service_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    port {&lt;br /&gt;
      port = 8080&lt;br /&gt;
      name = &amp;quot;web&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    selector = {&lt;br /&gt;
      app = var.name&lt;br /&gt;
    }&lt;br /&gt;
    type = &amp;quot;ClusterIP&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And then you can just access the port by &amp;lt;code&amp;gt;service_name:8080&amp;lt;/code&amp;gt;. Note that you can also set a &amp;lt;code&amp;gt;target_port&amp;lt;/code&amp;gt; if you want to change the port from the deployment and service (but like why?).&lt;br /&gt;
&lt;br /&gt;
Also giving it a &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; is important, so in the ingress we can just use the name instead of the port number itself, increasing readability.&lt;br /&gt;
&lt;br /&gt;
==== Network policies ====&lt;br /&gt;
&lt;br /&gt;
Now comes the pain. If a helm chart has a network policy, use that (but please actually read what permissions it gives).&lt;br /&gt;
&lt;br /&gt;
Network policies, either grant, or block network “ingress” (stuff going into the pod) and “egress” (stuff going out of the pod). By default, due to our security, ingress to our pods is denied (so anything in the cluster), but egress to the outside world is allowed.&lt;br /&gt;
&lt;br /&gt;
You then use either pod selectors or namespace selectors to allow traffic from a pod (which is how &amp;lt;code&amp;gt;enable_dns,lldap,mail&amp;lt;/code&amp;gt; works). In most cases egress can be left alone, unless you want to block a node from doing something in particular.&lt;br /&gt;
&lt;br /&gt;
As talked about in the next section, for postgres and valkey, we create these policies, limited to pods with the &amp;lt;code&amp;gt;${name}-${service}-client=true&amp;lt;/code&amp;gt; label allowing only inter-namespace communication.&lt;br /&gt;
&lt;br /&gt;
If you do have to write one, which I really hope you don’t, please read [https://kubernetes.io/docs/concepts/services-networking/network-policies/ kubernetes documentation on Network Policies].&lt;br /&gt;
&lt;br /&gt;
=== Postgres and Redis/Valkey ===&lt;br /&gt;
&lt;br /&gt;
We have utilities for [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/apps/postgres|postgres] and redis (through [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/apps/valkey|valkey] due to redis being really hard to run).&lt;br /&gt;
&lt;br /&gt;
If you require these, we highly recommend the above (though postgres needs moving away from bitnami due to the requirement of money for stability). These setup the network policies you need as well as generating secure passwords and you can look at the &amp;lt;code&amp;gt;outputs.tf&amp;lt;/code&amp;gt; file to see the outputs you can use from a module.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; If using this, for any pod accessing the data, it must be in the same namespace and have the labels &amp;lt;code&amp;gt;${name}-postgresql-client=true&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;${name}-valkey-client=true&amp;lt;/code&amp;gt;, otherwise the traffic will be denied by the network policies.&lt;br /&gt;
&lt;br /&gt;
E.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;database&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../postgres&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  namespace = var.namespace&lt;br /&gt;
  prefix    = var.name&lt;br /&gt;
  username  = &amp;quot;user&amp;quot;&lt;br /&gt;
  database  = &amp;quot;db_name&amp;quot;&lt;br /&gt;
  size      = &amp;quot;250Mi&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    helm = helm&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Create a secret with module.database.url&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = var.name&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = var.name&lt;br /&gt;
          // This **MUST** be defined otherwise you&#039;ll get weird errors&lt;br /&gt;
          froom-pg-postgresql-client = true&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // ...&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Side note: the namespace will also need to have &amp;lt;code&amp;gt;enable_dns&amp;lt;/code&amp;gt; to be able to access it&lt;br /&gt;
&lt;br /&gt;
=== Adding ingress ===&lt;br /&gt;
&lt;br /&gt;
Adding ingress to service basically means that you are making it accessible to the outside world.&lt;br /&gt;
&lt;br /&gt;
If you are doing this, please consider security heavily:&lt;br /&gt;
&lt;br /&gt;
* Can any user alter the DB? Are you doing proper type checking on the inputs?&lt;br /&gt;
* Do you make sure to not expose any secrets e.g. db urls&lt;br /&gt;
* Do you really need to expose this pod? Or can you leave it to k9s port forwarding?&lt;br /&gt;
&lt;br /&gt;
Then you need to ask:&lt;br /&gt;
&lt;br /&gt;
* Just expose it to people within the bath network e.g. on &amp;lt;code&amp;gt;*.k8s.bathcs.com&amp;lt;/code&amp;gt;&lt;br /&gt;
* Expose it to the whole world &amp;lt;code&amp;gt;*.bathcs.com&amp;lt;/code&amp;gt; (note coordination with backstage to get them to update their traefik will be necessary)&lt;br /&gt;
&lt;br /&gt;
Please expose to the utter minimum people.&lt;br /&gt;
&lt;br /&gt;
If exposing to the full network please note the flow is:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;app.bathcs.com&amp;lt;/code&amp;gt; &amp;amp;lt;- You should accept this domain in the ingress&lt;br /&gt;
* &amp;lt;code&amp;gt;app.bcss.su.bath.ac.uk&amp;lt;/code&amp;gt; &amp;amp;lt;- You should accept this domain in the ingress&lt;br /&gt;
* &amp;lt;code&amp;gt;app.k8s.bathcs.com&amp;lt;/code&amp;gt; &amp;amp;lt;- This is what the certificate you should be giving (due to this is hostname that backstage is requesting)&lt;br /&gt;
&lt;br /&gt;
Depending on the chosen level, please look at the [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/ingress|&amp;lt;code&amp;gt;utils/ingress/*&amp;lt;/code&amp;gt; modules] as these handle this most of this flow for you.&lt;br /&gt;
&lt;br /&gt;
E.g. making it internal to bath uni only:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;dns&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/dns_flow&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  subdomain = var.subdomain&lt;br /&gt;
  # We don&#039;t want public one, so don&#039;t specify cloudflare_zone_id&lt;br /&gt;
  k8s_cloudflare_zone_id = var.cloudflare_zone_id&lt;br /&gt;
&lt;br /&gt;
  # Expanded for effect&lt;br /&gt;
  domains = {&lt;br /&gt;
    uni = var.domains.uni&lt;br /&gt;
    k8s = var.domains.k8s&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    cloudflare = cloudflare,&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
module &amp;quot;ingress&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/tls&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  name                     = var.subdomain&lt;br /&gt;
  entry_points             = [&amp;quot;websecure&amp;quot;]&lt;br /&gt;
  namespace                = var.namespace&lt;br /&gt;
  host                     = module.dns.hosts.k8s&lt;br /&gt;
  additional_ingress_hosts = [module.dns.hosts.uni]&lt;br /&gt;
  service = {&lt;br /&gt;
    name = kubernetes_service_v1.module.metadata[0].name&lt;br /&gt;
    port = &amp;quot;http&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
  cert_issuer = var.cert_issuer&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes,&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If using a helm repo you may have to manually define the certificate, which is quite easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;cert&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    apiVersion = &amp;quot;cert-manager.io/v1&amp;quot;&lt;br /&gt;
    kind       = &amp;quot;Certificate&amp;quot;&lt;br /&gt;
    metadata = {&lt;br /&gt;
      name      = &amp;quot;${var.name}-cert&amp;quot;&lt;br /&gt;
      namespace = var.namespace&lt;br /&gt;
    }&lt;br /&gt;
    spec = {&lt;br /&gt;
      secretName = &amp;quot;${var.name}-cert-secret&amp;quot;&lt;br /&gt;
      issuerRef  = var.cert_issuer&lt;br /&gt;
      dnsNames   = [var.host]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which outputs the cert to the secret with name &amp;lt;code&amp;gt;${var.name}-cert-secret&amp;lt;/code&amp;gt;. You can also use &amp;lt;code&amp;gt;kubernetes_manifest.cert.manifest.spec.secretName&amp;lt;/code&amp;gt; (you can guess what I prefer).&lt;br /&gt;
&lt;br /&gt;
If you are making it publically accessible, you can use the full flow which does both dns and ingress records:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;ingress&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/full_flow&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  cloudflare_zone_id = var.cloudflare_zone_id&lt;br /&gt;
  namespace          = var.namespace&lt;br /&gt;
  domains            = var.domains&lt;br /&gt;
  subdomain          = var.subdomain&lt;br /&gt;
&lt;br /&gt;
  service = {&lt;br /&gt;
    name = kubernetes_service_v1.module.metadata[0].name&lt;br /&gt;
    port = &amp;quot;web&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
  cert_issuer = var.cert_issuer&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes,&lt;br /&gt;
    cloudflare = cloudflare&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Putting it behind authelia ====&lt;br /&gt;
&lt;br /&gt;
If the application doesn’t have oauth integration build in and you are wanting to protect it behind authelia you can add middleware of &amp;lt;code&amp;gt;auth-forwardauth-authelia@kubernetescrd&amp;lt;/code&amp;gt; to require them to go through authelia.&lt;br /&gt;
&lt;br /&gt;
You will then have to add an access control rule to authelia e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;rules = [&lt;br /&gt;
  {&lt;br /&gt;
    domain = &amp;amp;quot;app.k8s.bathcs.com&amp;amp;quot;&lt;br /&gt;
    subject = [&amp;amp;quot;group:boss-example-group&amp;amp;quot;, &amp;amp;quot;user:hw2210&amp;amp;quot;]&lt;br /&gt;
  },&lt;br /&gt;
]&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;: you can create and manage groups on [https://www.bath.ac.uk/groupmanager/ Bath&#039;s group manager], which creates a unix group that can be seen in the LDAP server.&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare dns records ===&lt;br /&gt;
&lt;br /&gt;
Cloudflare provider is an actual pain. You would’ve hoped it would be good, but its not amazing (has previously caused updates on every &amp;lt;code&amp;gt;apply&amp;lt;/code&amp;gt;). Basically when creating a dns record you should use the whole address e.g. &amp;lt;code&amp;gt;app.bathcs.com&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;app&amp;lt;/code&amp;gt;. This is because it will cause an update on the second application, changing the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; value (which we use to get the full domain).&lt;br /&gt;
&lt;br /&gt;
It sucks because theres an additional useless variable ontop of the &amp;lt;code&amp;gt;cloudflare_zone_id&amp;lt;/code&amp;gt; (yes you can use the “data” thing to solve this but still).&lt;br /&gt;
&lt;br /&gt;
Additionally for long TXT records, it will add additional quotes, and terraform will continually think you need to change that if you don’t yourself add the quotes into the data.&lt;br /&gt;
&lt;br /&gt;
=== Sending mail ===&lt;br /&gt;
&lt;br /&gt;
Sending mail is somewhat weird.&lt;br /&gt;
&lt;br /&gt;
Basically just because I can, the authentication is managed by &amp;lt;code&amp;gt;lldap&amp;lt;/code&amp;gt; (a really fast ldap implementation). This should not be confused with the ldap service that Authelia is hooked up with, as it is not.&lt;br /&gt;
&lt;br /&gt;
Once you’ve enabled mail in the namespace you then have to create an ldap user:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;user&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ldap/user&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  display   = &amp;quot;Example&amp;quot;&lt;br /&gt;
  username  = &amp;quot;example&amp;quot;&lt;br /&gt;
  group_ids = var.ldap_group_ids&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    lldap = lldap&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then use this to access the mail with &amp;lt;code&amp;gt;module.user.name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;module.user.password&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;module.user.email&amp;lt;/code&amp;gt;. &#039;&#039;&#039;NOTE&#039;&#039;&#039; due to it not being exposed, we must use the kubernetes domain certificate, which means that you normally have to disable tls verification it or set the expected domain to &amp;lt;code&amp;gt;bathcs.com&amp;lt;/code&amp;gt; (as seen in authelia).&lt;br /&gt;
&lt;br /&gt;
=== Creating an oauth client ===&lt;br /&gt;
&lt;br /&gt;
I have created a [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/oauth/client|OAuth client module] for generating all the secret data you need and generate the client config (under the &amp;lt;code&amp;gt;config&amp;lt;/code&amp;gt; output) you can then pass up and then into the authelia module (see &amp;lt;code&amp;gt;grafana&amp;lt;/code&amp;gt; as an example).&lt;br /&gt;
&lt;br /&gt;
But basically please read [https://www.authelia.com/integration/openid-connect/introduction/ the authelia oidc docs] for a full explanation of how it works.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Terraform&amp;diff=129</id>
		<title>Terraform</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Terraform&amp;diff=129"/>
		<updated>2026-06-04T11:02:30Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[BOSS]] uses terraform (actually [https://opentofu.org/ &amp;lt;code&amp;gt;opentofu&amp;lt;/code&amp;gt;]) to deploy all the resources onto [[BOSS/Hosting/Cluster their kubernetes cluster]], which tries to be the language in which you can deploy anything and everything and if you need to quickly redeploy a whole machine you can with a simple command. In practise it doesn’t work like that, but its good enough for our needs.&lt;br /&gt;
&lt;br /&gt;
You can find the [https://gitlab.bath.ac.uk/cs/int/terraform terraform repo on gitlab], this page was originally taken from the README as it got too long.&lt;br /&gt;
&lt;br /&gt;
== Other resources ==&lt;br /&gt;
&lt;br /&gt;
* [https://developer.hashicorp.com/terraform/tutorials Hashicorp (the maker of the closed source terraform) has some good tutorials on their site]&lt;br /&gt;
&lt;br /&gt;
Basically that’s it really, the rest of this will be looking at how to deploy an application with our configuration.&lt;br /&gt;
&lt;br /&gt;
== Terraform and its woes ==&lt;br /&gt;
&lt;br /&gt;
Before we get into making an app, I must briefly explain terraform and its benefits/issues/confusing behaviours.&lt;br /&gt;
&lt;br /&gt;
This expects you to have a rough idea around how terraform works. But here is a quick explainer: terraform is build around &#039;&#039;&#039;resources&#039;&#039;&#039;, provided by &#039;&#039;&#039;providers&#039;&#039;&#039;. These resources have a state stored locally in a state file, whether they are deployed, generated values etc. (note that these can literally be anything e.g. from random passwords to HTTP reequests to kubernetes resources to DNS records). These resources can then be organised into modules, which (can) have outputs from values generated by the resources. There are also “data”, but this is basically a reference to another resource which doesn’t have the controls.&lt;br /&gt;
&lt;br /&gt;
When deploying, terraform will then check the state of all the current deployed modules (even pinging servers if needed) and find anything that has changed (e.g. new resources or updated values) and deploy everything.&lt;br /&gt;
&lt;br /&gt;
=== Module structure ===&lt;br /&gt;
&lt;br /&gt;
Modules are the core of terraform and can be a bit tricky to get your head around, as initially they are quite limited (e.g. there is no such thing as a global variable).&lt;br /&gt;
&lt;br /&gt;
But basically a module has a list of inputs as and a list of outputs (and then providers). So it is expected that your module deploys some resources which are then used to output something. E.g. in [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/ldap/user|our ldap user module] the module generates a random password, creates the user and assigns them to a group, then outputs the username, email and password to be used later in another resource.&lt;br /&gt;
&lt;br /&gt;
So e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;user&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ldap/user&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  display   = &amp;quot;Example&amp;quot;&lt;br /&gt;
  username  = &amp;quot;example&amp;quot;&lt;br /&gt;
  group_ids = var.ldap_group_ids&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    // Note passing providers act somewhat like global constants, passing configuration (e.g. what ldap server we mean)&lt;br /&gt;
    // to the module&lt;br /&gt;
    lldap = lldap&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// You can then use the email by: module.user.email or password: module.user.password&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;: All files within a module (the folder) are treated as one global space, similar to how Go works. You can reference variables, locals and resources throughout all files within a module which makes it quite difficult to organise nicely.&lt;br /&gt;
&lt;br /&gt;
The modules are usually structure in the way:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;init.tf&amp;lt;/code&amp;gt; – Usually where your &amp;lt;code&amp;gt;providers&amp;lt;/code&amp;gt; go and if you are lazy (like me), everything else&lt;br /&gt;
* &amp;lt;code&amp;gt;vars.tf&amp;lt;/code&amp;gt;/&amp;lt;code&amp;gt;variables.tf&amp;lt;/code&amp;gt; – Where you put all your variables. As explained in [[#Variable madness|Variable madness]], I really don’t like this and so commonly ignore&lt;br /&gt;
* &amp;lt;code&amp;gt;outputs.tf&amp;lt;/code&amp;gt; – All your outputs go here&lt;br /&gt;
* &amp;lt;code&amp;gt;*.tf&amp;lt;/code&amp;gt; – Anything else, if you want to split it out nicely into other files&lt;br /&gt;
&lt;br /&gt;
=== Variable madness ===&lt;br /&gt;
&lt;br /&gt;
Terraform variables suck.&lt;br /&gt;
&lt;br /&gt;
Anyway, so basically terraform requires you do a full definition for every variable:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;variable &amp;quot;my_var&amp;quot; {&lt;br /&gt;
  type = string&lt;br /&gt;
&lt;br /&gt;
  description = &amp;quot;Something&amp;quot;&lt;br /&gt;
  nullable = false&lt;br /&gt;
&lt;br /&gt;
  sensitive = false # If something is sensitive MAKE THIS TRUE&lt;br /&gt;
}&lt;br /&gt;
// You can then later reference it with var.my_var&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This defines something that must be inputed by the user, either through the module or your &amp;lt;code&amp;gt;tfvars&amp;lt;/code&amp;gt; file (if in the root directory).&lt;br /&gt;
&lt;br /&gt;
Due to this verbosity, and sometimes complex nature of the interfaces I like to create, I have used the &amp;lt;code&amp;gt;object&amp;lt;/code&amp;gt; type e.g.:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;variable &amp;quot;my_var&amp;quot; {&lt;br /&gt;
  type = object({&lt;br /&gt;
    l = list(string)&lt;br /&gt;
    m = map(bool) # string -&amp;gt; bool. Same syntax as object, just more flexible&lt;br /&gt;
    s = set(string) # Yes this is different to list but using the same []&lt;br /&gt;
    option = optional(string, &amp;quot;my_default&amp;quot;)&lt;br /&gt;
  })&lt;br /&gt;
  // ...&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
But even then you can’t define nice defaults for each sub item and the description is for the whole variable, so it has to be done as so. This is just raw pain and not particularly great syntax in my opinion. Also you cannot enable/disable sensitive nature of values for sub items, this means the whole object must be defined as sensitive if you have one password.&lt;br /&gt;
&lt;br /&gt;
And due to the lack of global constants, you must define every variable in every sub project and duplicate the types (yes there is &#039;&#039;&#039;no&#039;&#039;&#039; way to define a type to use throughout the project).&lt;br /&gt;
&lt;br /&gt;
It is also recommended that you put all variables in a &amp;lt;code&amp;gt;vars.tf&amp;lt;/code&amp;gt; file. Which sure does make sense for small modules, but if its that small I find it easier to just chuck at the top of the &amp;lt;code&amp;gt;init.tf&amp;lt;/code&amp;gt; file (as the terraform syntax highlighter is soooo broken). Then if its large, I find it more useful to put the variables where they are actually used – but then again this is confusing because the syntax and tooling is so bad.&lt;br /&gt;
&lt;br /&gt;
Oh yeah sorry and then there are &#039;&#039;&#039;locals&#039;&#039;&#039; which are constants you can define from resources/variables and will be calculated when the information is ready. E.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;locals {&lt;br /&gt;
  temp_val = &amp;quot;hi&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
// Then you can reference with local.temp_val&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== Depends on and its pains ===&lt;br /&gt;
&lt;br /&gt;
The one issue with terraform is that its really slow with large projects with this. But the nature of the design encourages large projects (as you want to reference things throughout the smaller apps).&lt;br /&gt;
&lt;br /&gt;
This is due to it having to create a dependency graph where objects wait on their dependencies. These dependencies can be defined by &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt; in any resource or just referencing a value from another resource.&lt;br /&gt;
&lt;br /&gt;
This is really useful so deployments are not actually deployed until all the secrets are deployed. Due to my perferable of not repeating myself, I heavily use the inferred dependency from the referring to resource names e.g. &amp;lt;code&amp;gt;kubernetes_secret_v1.secret.metadata[0].name&amp;lt;/code&amp;gt; (yes this is why I don’t just do the simple thing and use the shorter name, its good to know where the value comes from).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT&#039;&#039;&#039; you cannot rely on this, as some things take time to actually deploy even if it says its successful. Therefore you may need to use timers instead. I usually don’t bother due to the numerous other issues with terraform making it so its not actually perfect so there’s minimal point actually making it easy to deploy from scratch.&lt;br /&gt;
&lt;br /&gt;
=== Timeouts ===&lt;br /&gt;
&lt;br /&gt;
If something goes wrong during deployment, e.g. you make a typo, you will have to wait the FULL timeout time. This is really painful when you typo the hostname to the db causing the pod to crashloop in a helm config and you have to wait 10 minutes for terraform to give up. You can Ctrl-C, Ctrl-C, but this causes more issues as you will have to manually intervene and delete the helm chart/deployment before you run the command again.&lt;br /&gt;
&lt;br /&gt;
Instead I recommend shortening the timeouts for the deployment/helm to one more applicable to the application. A lot of our first-party stuff usually deploys in a few seconds and if it doesn’t, something has gone very wrong.&lt;br /&gt;
&lt;br /&gt;
=== Commas or no commas? ===&lt;br /&gt;
&lt;br /&gt;
The terraform syntax is… interesting. Commas are optional in most cases. So I would recommend, not typing commas where they optional.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;BUT&#039;&#039;&#039; within lists/sets (basically between &amp;lt;code&amp;gt;[]&amp;lt;/code&amp;gt;) you have to type commas, and if this is across multiple lines &#039;&#039;&#039;PLEASE ADD TRAILING COMMAS&#039;&#039;&#039;. The reason? Git histories look sooooooo much better.&lt;br /&gt;
&lt;br /&gt;
== How to create a basic project ==&lt;br /&gt;
&lt;br /&gt;
=== Creating a namespace ===&lt;br /&gt;
&lt;br /&gt;
There is a handy util module for this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;example_namespace&amp;quot; {&lt;br /&gt;
  source = &amp;quot;./utils/namespace&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  name = &amp;quot;example&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  enable_dns = true&lt;br /&gt;
  enable_mail = true&lt;br /&gt;
  enable_lldap = true&lt;br /&gt;
  bkp = {&lt;br /&gt;
    // ...&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This allows you to enable or disable features for your namespace, e.g. if your pods need to communicate with the outside world, enabling the DNS. All of these features are disabled by default and its heavily encouraged to only enable the features if the namespace needs it.&lt;br /&gt;
&lt;br /&gt;
The next thing is to configure backups through this, to reduce our dependence and costs from our s3 provider, it is recommended that backups are disabled for all namespaces whos data can be regenerated (e.g. froom). If you do enable it, it is then encouraged that you disable backups for any database or pvc that you don’t need backing up with the &amp;lt;code&amp;gt;k8up.io/backup=false&amp;lt;/code&amp;gt; annotation (you may notice that all &amp;lt;code&amp;gt;valkey&amp;lt;/code&amp;gt; instances set this by default if you are using the &amp;lt;code&amp;gt;app/valkey&amp;lt;/code&amp;gt; module).&lt;br /&gt;
&lt;br /&gt;
==== Placement ====&lt;br /&gt;
&lt;br /&gt;
Within this repository, it is tradition to put the namespace creation at the highest level, e.g. &amp;lt;code&amp;gt;20_apps.tf&amp;lt;/code&amp;gt;. This means that the apps themselves do not control the namespace they are created in. It is mostly just a personal preference from me after years of configuring k8s on terraform.&lt;br /&gt;
&lt;br /&gt;
=== Using Helm ===&lt;br /&gt;
&lt;br /&gt;
Helm is by far the easiest way to deploy third-party tools, and is used throught this repo despite it’s drawbacks when combined with terraform (it’s just so easy).&lt;br /&gt;
&lt;br /&gt;
You just add helm to the providers list (which defines what terraform modules you are integrating with):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;terraform {&lt;br /&gt;
  required_providers {&lt;br /&gt;
    helm = {&lt;br /&gt;
      source  = &amp;quot;hashicorp/helm&amp;quot;&lt;br /&gt;
      version = &amp;quot;~&amp;gt;3.1.1&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then use the &amp;lt;code&amp;gt;helm_release&amp;lt;/code&amp;gt; resource, which takes the form:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;helm_release&amp;quot; &amp;quot;my_app&amp;quot; {&lt;br /&gt;
  name      = &amp;quot;my_app&amp;quot;&lt;br /&gt;
  namespace = var.namespace&lt;br /&gt;
&lt;br /&gt;
  repository = &amp;quot;https://charts.example.com&amp;quot;&lt;br /&gt;
  chart      = &amp;quot;the_app&amp;quot;&lt;br /&gt;
  version    = &amp;quot;version&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  values = [yamlencode({&lt;br /&gt;
    // Values go here written within the terraform config language&lt;br /&gt;
  })]&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This deploys all the resources an application needs and manages and restarting if a secret or config map changes, providing all the configuration at your fingertips.&lt;br /&gt;
&lt;br /&gt;
However this comes at a cost:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;SECRETS SHOULD NOT GO IN THE HELM CONFIG&#039;&#039;&#039;. This is a big one, all values are easily accessible unencrypted on the cluster, therefore any secrets &#039;&#039;&#039;MUST&#039;&#039;&#039; go in a &amp;lt;code&amp;gt;kubernetes_secrets_v1&amp;lt;/code&amp;gt; object and you should use a &amp;lt;code&amp;gt;secretsRef&amp;lt;/code&amp;gt; or similar to link it. If the helm chart does not support this &#039;&#039;&#039;DO NOT USE IT&#039;&#039;&#039;. Helm also stores the history of all values, therefore if you put it temporarily within helm values for testing, you must to a password rotation.&lt;br /&gt;
* You have no power over the types of resources and the structure in which it deploys. This means that if a feature or support for our strict network policies are not implemented, you have to either not use the helm chart completely or fork your own (which we definitely don’t want to do).&lt;br /&gt;
* If the helm chart gets deleted, all pvc related &#039;&#039;might&#039;&#039; also get deleted (unless they have the &amp;lt;code&amp;gt;Retain&amp;lt;/code&amp;gt; policy, which should be the case for everything).&lt;br /&gt;
* Sometimes they don’t have the proper security contexts/network policies by default so you will have to add them youself (see the below section)&lt;br /&gt;
&lt;br /&gt;
Overall, helm is pretty good, just use with caution and understand what templates you are inflicting. Note, you will probably have to get pretty good at reading not only default values, but schemas and the templating language of helm itself, as sometimes the charts are not particularly well documented.&lt;br /&gt;
&lt;br /&gt;
=== Using kubernetes ===&lt;br /&gt;
&lt;br /&gt;
For this you need to understand a bit of structure of how kubernetes works. I will assume that you are deploying a pod. If that pod needs storage attached (and not through SQL or Redis), then you will need to use a [https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ &amp;lt;code&amp;gt;StatefulSet&amp;lt;/code&amp;gt;]. If you have no storage or are just communicating with a postgres server or redis, you can instead use a [https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ &amp;lt;code&amp;gt;Deployment&amp;lt;/code&amp;gt;].&lt;br /&gt;
&lt;br /&gt;
The difference between these two concepts are not particularly seen in the world of a single node cluster, but basically deployments are free to spin up another even if the previous one is still terminating or if the node is non responsive. On the other hand, statefulsets must ensure that no two nodes are trying to access the same data, therefore cannot automatically start up if a node goes down.&lt;br /&gt;
&lt;br /&gt;
This also means that it is much easier to scale a deployment to multiple nodes, vs a statefulsets which must have separate volumes per pod.&lt;br /&gt;
&lt;br /&gt;
Anyway, both statefulsets and deployments have a template configuration for creating the pod associated with itself. This pod has a label which is used to monitor and track the associated pods with its parent. So the structure is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;my_deployment&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    replicas = 1&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = &amp;quot;the_deployment&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    // This doesn&#039;t really matter in a one node cluster with a replicas = 1&lt;br /&gt;
    strategy {&lt;br /&gt;
      type = &amp;quot;RollingUpdate&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = &amp;quot;the_deployment&amp;quot;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          name              = &amp;quot;my_deployment&amp;quot;&lt;br /&gt;
          image             = &amp;quot;bathbcss/my_image:latest&amp;quot;&lt;br /&gt;
          image_pull_policy = &amp;quot;Always&amp;quot; // Should only be set if the above is &amp;quot;latest&amp;quot;&lt;br /&gt;
&lt;br /&gt;
          // This should be the default security context to comply with our pod security policies&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // This is how the pod is checked its alive, so if something happens,&lt;br /&gt;
          // e.g. job which causes it to become unresponsive, it will be automatically killed off and replaced&lt;br /&gt;
          liveness_probe {&lt;br /&gt;
            http_get {&lt;br /&gt;
              path = &amp;quot;/healthz&amp;quot;&lt;br /&gt;
              port = 8080&lt;br /&gt;
            }&lt;br /&gt;
            initial_delay_seconds = 5&lt;br /&gt;
            period_seconds        = 10&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // This allows the tracking of when the pod starts, so we wait until the pod is ready to receive requests&lt;br /&gt;
          startup_probe {&lt;br /&gt;
            http_get {&lt;br /&gt;
              path = &amp;quot;/healthz&amp;quot;&lt;br /&gt;
              port = 8080&lt;br /&gt;
            }&lt;br /&gt;
            // 3 * 30 = 90 seconds to start&lt;br /&gt;
            failure_threshold = 30&lt;br /&gt;
            // If it takes a while to startup, increase this time&lt;br /&gt;
            period_seconds = 3&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // If exposing a port the port to expose&lt;br /&gt;
          port {&lt;br /&gt;
            container_port = 8080&lt;br /&gt;
            // Make sure to give it a name so we can use the name in services&lt;br /&gt;
            name           = &amp;quot;web&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          // env and env_from definitions&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&#039;&#039;Quick side note: &amp;lt;code&amp;gt;kubernetes_\*_v1&amp;lt;/code&amp;gt;is the preferred resouce name, any resource that does not have&amp;lt;code&amp;gt;\_v1&amp;lt;/code&amp;gt; on the end is deprecated and should not be used.&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
It is recommended that the version of the image is actually set and &amp;lt;code&amp;gt;latest&amp;lt;/code&amp;gt; is not used, however to reduce the admin overhead, for internal projects it can be easier to set to &amp;lt;code&amp;gt;latest&amp;lt;/code&amp;gt; with an image pull policy of &amp;lt;code&amp;gt;Always&amp;lt;/code&amp;gt;. However this means if you want the latest version, you must have access to the cluster to restart a pod.&lt;br /&gt;
&lt;br /&gt;
If you are exposing pods, this should be tied with a &amp;lt;code&amp;gt;Service&amp;lt;/code&amp;gt;, as seen below.&lt;br /&gt;
&lt;br /&gt;
==== Security context ====&lt;br /&gt;
&lt;br /&gt;
As you will notice in the example above, we have a security context set. This is &#039;&#039;&#039;required&#039;&#039;&#039; by the pod security contenxt, otherwise it will not deploy. In most cases you can copy either of the two following policies, depending on whether it is within a kubernetes resource or helm/kubernetes manifest resource:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  security_context {&lt;br /&gt;
    run_as_user                = 1000&lt;br /&gt;
    run_as_non_root            = true&lt;br /&gt;
    allow_privilege_escalation = false&lt;br /&gt;
    seccomp_profile {&lt;br /&gt;
      type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    capabilities {&lt;br /&gt;
      drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    securityContext = {&lt;br /&gt;
      runAsUser                = 1000&lt;br /&gt;
      runAsNonRoot             = true&lt;br /&gt;
      allowPrivilegeEscalation = false&lt;br /&gt;
      seccompProfile = {&lt;br /&gt;
        type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      capabilities = {&lt;br /&gt;
        drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that we are running as a user (not root), setting the default seccompProfile (you &#039;&#039;should&#039;&#039; only need the default unless you are doing weird things with the host machine) as well as dropping all capabilities (you may need to add some back in but I will leave to you as you probably know more than me – NOTE: Some are disabled by our pod security policy but can be override with &amp;lt;code&amp;gt;baseline&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
==== Liveness and startup probe ====&lt;br /&gt;
&lt;br /&gt;
The liveness and startup probes are not necessary, but is a nice to have. The liveness probe allows the cluster to detect if a pod becomes unresponsive and is then able to kill it if that is the case. Whereas a startup probe makes it so the cluster knows exactly when the pod is able to receive responses.&lt;br /&gt;
&lt;br /&gt;
Please see the [https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ kubernetes docs on probes] for more information on the options. But in most cases the HTTP get option should suffice, which just looks for a status 2xx code.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  liveness_probe {&lt;br /&gt;
    http_get {&lt;br /&gt;
      port = 8080&lt;br /&gt;
    }&lt;br /&gt;
    initial_delay_seconds = 5&lt;br /&gt;
    period_seconds        = 10&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  startup_probe {&lt;br /&gt;
    http_get {&lt;br /&gt;
      port = 8080&lt;br /&gt;
    }&lt;br /&gt;
    failure_threshold = 30&lt;br /&gt;
    period_seconds = 3&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    livenessProbe = {&lt;br /&gt;
      httpGet = {&lt;br /&gt;
        port = 8080&lt;br /&gt;
      }&lt;br /&gt;
      initialDelaySeconds = 5&lt;br /&gt;
      periodSeconds       = 10&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    startupProbe = {&lt;br /&gt;
      httpGet = {&lt;br /&gt;
        port = 8080&lt;br /&gt;
      }&lt;br /&gt;
      failureThreshold = 30&lt;br /&gt;
      periodSeconds    = 3&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Environmental variables ====&lt;br /&gt;
&lt;br /&gt;
When configuring deployments, you will want to set environmental variables. There are a few ways to do it, but note &#039;&#039;&#039;ANY PASSWORDS/API KEYS GO IN SECRETS&#039;&#039;&#039; not the environmental variables. As you will see I will example how to do this.&lt;br /&gt;
&lt;br /&gt;
By default the &amp;lt;code&amp;gt;env&amp;lt;/code&amp;gt; list can be used to set a single environmental variable e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env {&lt;br /&gt;
    name = &amp;quot;TEST&amp;quot;&lt;br /&gt;
    value = &amp;quot;my_value&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
However, this is quite verbose and takes a lot of space, so if you are configuring a lot of variables or have secrets, you will want to use the &amp;lt;code&amp;gt;env_from&amp;lt;/code&amp;gt; list. This allows you to reference a config map or secrets (this is the most basic form).&lt;br /&gt;
&lt;br /&gt;
These look like:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_secret_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;deployment-db&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  data = {&lt;br /&gt;
    DATABASE_URL = module.database.url&lt;br /&gt;
  }&lt;br /&gt;
  type = &amp;quot;Opaque&amp;quot;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_config_map_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = &amp;quot;deployment-config&amp;quot;&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  data = {&lt;br /&gt;
    PUBLIC_VALUE   = &amp;quot;yoooo&amp;quot;&lt;br /&gt;
    ROCKET_ADDRESS = &amp;quot;::&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env_from {&lt;br /&gt;
    secret_ref {&lt;br /&gt;
      name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  env_from {&lt;br /&gt;
    config_map_ref {&lt;br /&gt;
      name = kubernetes_config_map_v1.module.metadata[0].name&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    // ....&lt;br /&gt;
&lt;br /&gt;
    envFrom = [&lt;br /&gt;
      {&lt;br /&gt;
        secretRef = {&lt;br /&gt;
          name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
      {&lt;br /&gt;
        configMapRef = {&lt;br /&gt;
          name = kubernetes_config_map_v1.module.metadata[0].name&lt;br /&gt;
        }&lt;br /&gt;
      },&lt;br /&gt;
    ]&lt;br /&gt;
&lt;br /&gt;
    // ....&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Note that you can set the value of a environmental variable from a secret on an individual basis, which can be useful if you are storing environmental variables as well as files inside your secret. E.g:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  // ....&lt;br /&gt;
&lt;br /&gt;
  env {&lt;br /&gt;
    name = &amp;quot;DB_PASSWORD&amp;quot;&lt;br /&gt;
    value_from {&lt;br /&gt;
      secret_key_ref {&lt;br /&gt;
        name = kubernetes_secret_v1.module.metadata[0].name&lt;br /&gt;
        key  = &amp;quot;password&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  // ....&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Third-party CRDs ====&lt;br /&gt;
&lt;br /&gt;
Now this is is where terraform becomes less good. Basically when deploying using the kubernetes API, the checks will make sure the CRDs (so the like api and kind are installed and supported on the kubernetes cluster). This means that you won’t even be able to run.&lt;br /&gt;
&lt;br /&gt;
Basically it means that you need to comment out manifests that reference these resources until the CRDs are deployed (usually though a helm chart or something).&lt;br /&gt;
&lt;br /&gt;
==== PVCs ====&lt;br /&gt;
&lt;br /&gt;
Please remember &#039;&#039;&#039;ANY PASSWORDS/API KEYS/CERTIFICATES GO IN SECRETS&#039;&#039;&#039; not in the storage (this also means its configurable by us and yes they can be mounted as read only volumes).&lt;br /&gt;
&lt;br /&gt;
So, kubernetes storage works around persistant volumes which are requested by persistant volume claims. On our k3s single node, we are just using the k3s filesystem class. This means it’s a bit basic but does the job.&lt;br /&gt;
&lt;br /&gt;
Things to note:&lt;br /&gt;
&lt;br /&gt;
* You probably should be manually creating persistant volume claims (and definitely not persistant volumes), instead using &amp;lt;code&amp;gt;statefulsets&amp;lt;/code&amp;gt;&lt;br /&gt;
* It’s really hard to change persistant volumes post fact, so please go through testing phase if you are unsure about anything.&lt;br /&gt;
* K3s does not support the storage limit, so please &#039;&#039;&#039;DON’T RELY ON IT&#039;&#039;&#039; to stop abusive behaviour.&lt;br /&gt;
* If you are defining yourself, do not accidentally make your deployment depend on the persistant volume claim, as the pvc will not be created until it is used in something. See [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/services/mail/mailserver.tf|the mailserver module] of how to handle it. &#039;&#039;&#039;NOTE&#039;&#039;&#039;: If you reference the pvc config in your deployment, terraform will add that automatically to the &amp;lt;code&amp;gt;depends_on&amp;lt;/code&amp;gt; list.&lt;br /&gt;
* If it is critical data you will need to &#039;&#039;&#039;manually update the pv to “retain” its data&#039;&#039;&#039; if the pvc gets deleted. This just adds a bit of safety if you mess up a deployment. This can be done in &amp;lt;code&amp;gt;k9s&amp;lt;/code&amp;gt; by an admin, updating the &amp;lt;code&amp;gt;persistentVolumeReclaimPolicy&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;Retain&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Delete&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
So how you should be using pvc, in statefulsets:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_stateful_set_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    service_name = var.name&lt;br /&gt;
    replicas     = 1&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = var.name&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = var.name&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          name  = &amp;quot;my_app&amp;quot;&lt;br /&gt;
          image = &amp;quot;bathbcss/my_app:1.0.0&amp;quot;&lt;br /&gt;
&lt;br /&gt;
          // ...&lt;br /&gt;
&lt;br /&gt;
          volume_mount {&lt;br /&gt;
            name       = &amp;quot;data&amp;quot;&lt;br /&gt;
            mount_path = &amp;quot;/data&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    volume_claim_template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        name = &amp;quot;data&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
      spec {&lt;br /&gt;
        access_modes = [&amp;quot;ReadWriteOnce&amp;quot;]&lt;br /&gt;
        resources {&lt;br /&gt;
          requests = {&lt;br /&gt;
            storage = &amp;quot;10Gi&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Services ====&lt;br /&gt;
&lt;br /&gt;
Servies are the first way to adding an ingress, and helps give a common endpoint if you have multiple pods running (though we don’t do this). If using within the cluster you can access a service with the address &amp;lt;code&amp;gt;service_name.namespace.svc.cluster.local&amp;lt;/code&amp;gt;. Note that if you are accessing it from within the same namespace you can just use the &amp;lt;code&amp;gt;service_name&amp;lt;/code&amp;gt; as the hostname (and it is better on a network policy basis).&lt;br /&gt;
&lt;br /&gt;
For a namespace you just need a label to select on (note we have to define an &amp;lt;code&amp;gt;app&amp;lt;/code&amp;gt; label for the statefulset/deployment anyway so you can just use this).&lt;br /&gt;
&lt;br /&gt;
So it should look like this:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_service_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    port {&lt;br /&gt;
      port = 8080&lt;br /&gt;
      name = &amp;quot;web&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
    selector = {&lt;br /&gt;
      app = var.name&lt;br /&gt;
    }&lt;br /&gt;
    type = &amp;quot;ClusterIP&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
And then you can just access the port by &amp;lt;code&amp;gt;service_name:8080&amp;lt;/code&amp;gt;. Note that you can also set a &amp;lt;code&amp;gt;target_port&amp;lt;/code&amp;gt; if you want to change the port from the deployment and service (but like why?).&lt;br /&gt;
&lt;br /&gt;
Also giving it a &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; is important, so in the ingress we can just use the name instead of the port number itself, increasing readability.&lt;br /&gt;
&lt;br /&gt;
==== Network policies ====&lt;br /&gt;
&lt;br /&gt;
Now comes the pain. If a helm chart has a network policy, use that (but please actually read what permissions it gives).&lt;br /&gt;
&lt;br /&gt;
Network policies, either grant, or block network “ingress” (stuff going into the pod) and “egress” (stuff going out of the pod). By default, due to our security, ingress to our pods is denied (so anything in the cluster), but egress to the outside world is allowed.&lt;br /&gt;
&lt;br /&gt;
You then use either pod selectors or namespace selectors to allow traffic from a pod (which is how &amp;lt;code&amp;gt;enable_dns,lldap,mail&amp;lt;/code&amp;gt; works). In most cases egress can be left alone, unless you want to block a node from doing something in particular.&lt;br /&gt;
&lt;br /&gt;
As talked about in the next section, for postgres and valkey, we create these policies, limited to pods with the &amp;lt;code&amp;gt;${name}-${service}-client=true&amp;lt;/code&amp;gt; label allowing only inter-namespace communication.&lt;br /&gt;
&lt;br /&gt;
If you do have to write one, which I really hope you don’t, please read [https://kubernetes.io/docs/concepts/services-networking/network-policies/ kubernetes documentation on Network Policies].&lt;br /&gt;
&lt;br /&gt;
=== Postgres and Redis/Valkey ===&lt;br /&gt;
&lt;br /&gt;
We have utilities for [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/apps/postgres|postgres] and redis (through [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/apps/valkey|valkey] due to redis being really hard to run).&lt;br /&gt;
&lt;br /&gt;
If you require these, we highly recommend the above (though postgres needs moving away from bitnami due to the requirement of money for stability). These setup the network policies you need as well as generating secure passwords and you can look at the &amp;lt;code&amp;gt;outputs.tf&amp;lt;/code&amp;gt; file to see the outputs you can use from a module.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; If using this, for any pod accessing the data, it must be in the same namespace and have the labels &amp;lt;code&amp;gt;${name}-postgresql-client=true&amp;lt;/code&amp;gt; and/or &amp;lt;code&amp;gt;${name}-valkey-client=true&amp;lt;/code&amp;gt;, otherwise the traffic will be denied by the network policies.&lt;br /&gt;
&lt;br /&gt;
E.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;database&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../postgres&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  namespace = var.namespace&lt;br /&gt;
  prefix    = var.name&lt;br /&gt;
  username  = &amp;quot;user&amp;quot;&lt;br /&gt;
  database  = &amp;quot;db_name&amp;quot;&lt;br /&gt;
  size      = &amp;quot;250Mi&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    helm = helm&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Create a secret with module.database.url&lt;br /&gt;
&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;module&amp;quot; {&lt;br /&gt;
  metadata {&lt;br /&gt;
    name      = var.name&lt;br /&gt;
    namespace = var.namespace&lt;br /&gt;
  }&lt;br /&gt;
  spec {&lt;br /&gt;
    selector {&lt;br /&gt;
      match_labels = {&lt;br /&gt;
        app = var.name&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    template {&lt;br /&gt;
      metadata {&lt;br /&gt;
        labels = {&lt;br /&gt;
          app = var.name&lt;br /&gt;
          // This **MUST** be defined otherwise you&#039;ll get weird errors&lt;br /&gt;
          froom-pg-postgresql-client = true&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      // ...&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Side note: the namespace will also need to have &amp;lt;code&amp;gt;enable_dns&amp;lt;/code&amp;gt; to be able to access it&lt;br /&gt;
&lt;br /&gt;
=== Adding ingress ===&lt;br /&gt;
&lt;br /&gt;
Adding ingress to service basically means that you are making it accessible to the outside world.&lt;br /&gt;
&lt;br /&gt;
If you are doing this, please consider security heavily:&lt;br /&gt;
&lt;br /&gt;
* Can any user alter the DB? Are you doing proper type checking on the inputs?&lt;br /&gt;
* Do you make sure to not expose any secrets e.g. db urls&lt;br /&gt;
* Do you really need to expose this pod? Or can you leave it to k9s port forwarding?&lt;br /&gt;
&lt;br /&gt;
Then you need to ask:&lt;br /&gt;
&lt;br /&gt;
* Just expose it to people within the bath network e.g. on &amp;lt;code&amp;gt;*.k8s.bathcs.com&amp;lt;/code&amp;gt;&lt;br /&gt;
* Expose it to the whole world &amp;lt;code&amp;gt;*.bathcs.com&amp;lt;/code&amp;gt; (note coordination with backstage to get them to update their traefik will be necessary)&lt;br /&gt;
&lt;br /&gt;
Please expose to the utter minimum people.&lt;br /&gt;
&lt;br /&gt;
If exposing to the full network please note the flow is:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;app.bathcs.com&amp;lt;/code&amp;gt; &amp;amp;lt;- You should accept this domain in the ingress&lt;br /&gt;
* &amp;lt;code&amp;gt;app.bcss.su.bath.ac.uk&amp;lt;/code&amp;gt; &amp;amp;lt;- You should accept this domain in the ingress&lt;br /&gt;
* &amp;lt;code&amp;gt;app.k8s.bathcs.com&amp;lt;/code&amp;gt; &amp;amp;lt;- This is what the certificate you should be giving (due to this is hostname that backstage is requesting)&lt;br /&gt;
&lt;br /&gt;
Depending on the chosen level, please look at the [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/ingress|&amp;lt;code&amp;gt;utils/ingress/*&amp;lt;/code&amp;gt; modules] as these handle this most of this flow for you.&lt;br /&gt;
&lt;br /&gt;
E.g. making it internal to bath uni only:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;dns&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/dns_flow&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  subdomain = var.subdomain&lt;br /&gt;
  # We don&#039;t want public one, so don&#039;t specify cloudflare_zone_id&lt;br /&gt;
  k8s_cloudflare_zone_id = var.cloudflare_zone_id&lt;br /&gt;
&lt;br /&gt;
  # Expanded for effect&lt;br /&gt;
  domains = {&lt;br /&gt;
    uni = var.domains.uni&lt;br /&gt;
    k8s = var.domains.k8s&lt;br /&gt;
  }&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    cloudflare = cloudflare,&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
module &amp;quot;ingress&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/tls&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  name                     = var.subdomain&lt;br /&gt;
  entry_points             = [&amp;quot;websecure&amp;quot;]&lt;br /&gt;
  namespace                = var.namespace&lt;br /&gt;
  host                     = module.dns.hosts.k8s&lt;br /&gt;
  additional_ingress_hosts = [module.dns.hosts.uni]&lt;br /&gt;
  service = {&lt;br /&gt;
    name = kubernetes_service_v1.module.metadata[0].name&lt;br /&gt;
    port = &amp;quot;http&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
  cert_issuer = var.cert_issuer&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes,&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If using a helm repo you may have to manually define the certificate, which is quite easy:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;resource &amp;quot;kubernetes_manifest&amp;quot; &amp;quot;cert&amp;quot; {&lt;br /&gt;
  manifest = {&lt;br /&gt;
    apiVersion = &amp;quot;cert-manager.io/v1&amp;quot;&lt;br /&gt;
    kind       = &amp;quot;Certificate&amp;quot;&lt;br /&gt;
    metadata = {&lt;br /&gt;
      name      = &amp;quot;${var.name}-cert&amp;quot;&lt;br /&gt;
      namespace = var.namespace&lt;br /&gt;
    }&lt;br /&gt;
    spec = {&lt;br /&gt;
      secretName = &amp;quot;${var.name}-cert-secret&amp;quot;&lt;br /&gt;
      issuerRef  = var.cert_issuer&lt;br /&gt;
      dnsNames   = [var.host]&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Which outputs the cert to the secret with name &amp;lt;code&amp;gt;${var.name}-cert-secret&amp;lt;/code&amp;gt;. You can also use &amp;lt;code&amp;gt;kubernetes_manifest.cert.manifest.spec.secretName&amp;lt;/code&amp;gt; (you can guess what I prefer).&lt;br /&gt;
&lt;br /&gt;
If you are making it publically accessible, you can use the full flow which does both dns and ingress records:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;ingress&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ingress/full_flow&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  cloudflare_zone_id = var.cloudflare_zone_id&lt;br /&gt;
  namespace          = var.namespace&lt;br /&gt;
  domains            = var.domains&lt;br /&gt;
  subdomain          = var.subdomain&lt;br /&gt;
&lt;br /&gt;
  service = {&lt;br /&gt;
    name = kubernetes_service_v1.module.metadata[0].name&lt;br /&gt;
    port = &amp;quot;web&amp;quot;&lt;br /&gt;
  }&lt;br /&gt;
  cert_issuer = var.cert_issuer&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    kubernetes = kubernetes,&lt;br /&gt;
    cloudflare = cloudflare&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
==== Putting it behind authelia ====&lt;br /&gt;
&lt;br /&gt;
If the application doesn’t have oauth integration build in and you are wanting to protect it behind authelia you can add middleware of &amp;lt;code&amp;gt;auth-forwardauth-authelia@kubernetescrd&amp;lt;/code&amp;gt; to require them to go through authelia.&lt;br /&gt;
&lt;br /&gt;
You will then have to add an access control rule to authelia e.g.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;rules = [&lt;br /&gt;
  {&lt;br /&gt;
    domain = &amp;amp;quot;app.k8s.bathcs.com&amp;amp;quot;&lt;br /&gt;
    subject = [&amp;amp;quot;user:hw2210&amp;amp;quot;]&lt;br /&gt;
  },&lt;br /&gt;
]&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;NOTE&#039;&#039;&#039;: Sadly we can’t use groups and so we have to limit by user directly.&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare dns records ===&lt;br /&gt;
&lt;br /&gt;
Cloudflare provider is an actual pain. You would’ve hoped it would be good, but its not amazing (has previously caused updates on every &amp;lt;code&amp;gt;apply&amp;lt;/code&amp;gt;). Basically when creating a dns record you should use the whole address e.g. &amp;lt;code&amp;gt;app.bathcs.com&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;app&amp;lt;/code&amp;gt;. This is because it will cause an update on the second application, changing the &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt; value (which we use to get the full domain).&lt;br /&gt;
&lt;br /&gt;
It sucks because theres an additional useless variable ontop of the &amp;lt;code&amp;gt;cloudflare_zone_id&amp;lt;/code&amp;gt; (yes you can use the “data” thing to solve this but still).&lt;br /&gt;
&lt;br /&gt;
Additionally for long TXT records, it will add additional quotes, and terraform will continually think you need to change that if you don’t yourself add the quotes into the data.&lt;br /&gt;
&lt;br /&gt;
=== Sending mail ===&lt;br /&gt;
&lt;br /&gt;
Sending mail is somewhat weird.&lt;br /&gt;
&lt;br /&gt;
Basically just because I can, the authentication is managed by &amp;lt;code&amp;gt;lldap&amp;lt;/code&amp;gt; (a really fast ldap implementation). This should not be confused with the ldap service that Authelia is hooked up with, as it is not.&lt;br /&gt;
&lt;br /&gt;
Once you’ve enabled mail in the namespace you then have to create an ldap user:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;module &amp;quot;user&amp;quot; {&lt;br /&gt;
  source = &amp;quot;../../utils/ldap/user&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  display   = &amp;quot;Example&amp;quot;&lt;br /&gt;
  username  = &amp;quot;example&amp;quot;&lt;br /&gt;
  group_ids = var.ldap_group_ids&lt;br /&gt;
&lt;br /&gt;
  providers = {&lt;br /&gt;
    lldap = lldap&lt;br /&gt;
  }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You can then use this to access the mail with &amp;lt;code&amp;gt;module.user.name&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;module.user.password&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;module.user.email&amp;lt;/code&amp;gt;. &#039;&#039;&#039;NOTE&#039;&#039;&#039; due to it not being exposed, we must use the kubernetes domain certificate, which means that you normally have to disable tls verification it or set the expected domain to &amp;lt;code&amp;gt;bathcs.com&amp;lt;/code&amp;gt; (as seen in authelia).&lt;br /&gt;
&lt;br /&gt;
=== Creating an oauth client ===&lt;br /&gt;
&lt;br /&gt;
I have created a [https://gitlab.bath.ac.uk/cs/int/terraform/-/blob/main/utils/oauth/client|OAuth client module] for generating all the secret data you need and generate the client config (under the &amp;lt;code&amp;gt;config&amp;lt;/code&amp;gt; output) you can then pass up and then into the authelia module (see &amp;lt;code&amp;gt;grafana&amp;lt;/code&amp;gt; as an example).&lt;br /&gt;
&lt;br /&gt;
But basically please read [https://www.authelia.com/integration/openid-connect/introduction/ the authelia oidc docs] for a full explanation of how it works.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=128</id>
		<title>Kubernetes</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Kubernetes&amp;diff=128"/>
		<updated>2026-06-04T10:56:45Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Add text for general knowledge section&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== General knowledge ==&lt;br /&gt;
This tries to cover some basic concepts, focusing on common confusion, but it will skip over a lot of the general knowledge information such as secrets and configmaps. The kubernete&#039;s documentation is pretty good, though difficult to read at some points, but there are loads of great tutorials explaining how kubernetes works.&lt;br /&gt;
&lt;br /&gt;
=== Pod vs Container ===&lt;br /&gt;
A common confusion is that [https://kubernetes.io/docs/concepts/workloads/pods/ pod]&#039;s are containers in kubernetes. This is not exactly true, a pod is a general group of linux namespaces which can host multiple containers. This means you can have a container that writes to a directory and another container that reads from that directory in the same pod. This can be very powerful, but in a lot of cases can be ignored.&lt;br /&gt;
&lt;br /&gt;
But it is key to point out that a Pod is a resource that is created by other kubernetes resources. They are a group of processes running, once they die the pod is deleted and forgotten about. Therefore you should not be creating pods directly, instead you should be using deployments, statefulsets, cronjobs or even jobs. All these resources create generate a pod as their lifecycle and will restart/recreate the pod if it fails.&lt;br /&gt;
&lt;br /&gt;
=== Statefulset vs deployment ===&lt;br /&gt;
Another key understanding is the difference between statefulsets and deployments, as statefulsets can cause some confusion in how they work. The difference is more applicable to multinode clusters but are still key to the structure of kubernetes.&lt;br /&gt;
&lt;br /&gt;
Effectively, a statefulset is a deployment with writable volumes - known as persistent volumes (PV). Having the ability to write to volumes can cause race conditions when multiple pods across nodes are writing to the same file. This is where statefulsets come in, they lock volumes and so they can only be used by one node and one pod, with scaling creating new persistant volumes which are stored separately. This means that if you scale a statefulset that relies on shared knowledge in the volume, half your requests will have one set of data and the other half will have another.&lt;br /&gt;
&lt;br /&gt;
This obviously is quite a big disadvantage and can lead to confusing behaviour when a node is not configured to shutdown safely and taint itself, moving all the statefulsets off of itself before it shutsdown - if PV is locked by a node and pod, it cannot be deployed to another cluster.&lt;br /&gt;
&lt;br /&gt;
Therefore, this is where deployments come in, they, usually, do not have associated persistent volumes, allowing for easy horizontal scaling. For storing shared data, they should connect to a database on another node which can be more compatible with statefulsets when configured correctly.&lt;br /&gt;
&lt;br /&gt;
Both of these resources will create pods and redeploy them if they crash.&lt;br /&gt;
&lt;br /&gt;
==== Liveness/Startup probes ====&lt;br /&gt;
Liveness and startup probes can be defined on pods, and these let kubernetes know if a pod has started correctly and if it still is alive. For example, some deployments might take a while to start up and configure everything before it starts serving content and so when restarting, this can cause some downtime. Downtime is what we are trying to avoid and so by using a startup probe, kubernetes knows that this application is ready, and so it will only terminate the previous node once the new one is started up resulting in zero downtime!&lt;br /&gt;
&lt;br /&gt;
The liveness probe on the other hand periodically checks whether the pod is still alive. This means that if it suddenly stops responding due to a long database query, kubernetes can detect that and replace the pod with another further reducing downtime. However, this usually suggests something else is wrong with the application and so this should be investigated and fixed.&lt;br /&gt;
&lt;br /&gt;
==== Security Context ====&lt;br /&gt;
{{Note|text=Within [[BOSS/Hosting/Cluster|BOSS&#039;s kubernetes cluster]], we define a security policy which requires all pods to correctly define their security context and make sure that it is not running as root.}}&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ security context] defines what privileges the pod has when running, we effectively want this to be as minimal as possible to reduce attack surface area. E.g.&lt;br /&gt;
&lt;br /&gt;
* Run as user&lt;br /&gt;
* Don&#039;t allow privilege escalation&lt;br /&gt;
* Properly define seccomp policy&lt;br /&gt;
* Default SELinux container context&lt;br /&gt;
* Drop all capabilities&lt;br /&gt;
&lt;br /&gt;
However this can cause issues with third-party applications which commonly do some questionable things, e.g. require running as root or changing the uid. But for our pods you can mostly just copy and paste:&amp;lt;syntaxhighlight lang=&amp;quot;terraform&amp;quot;&amp;gt;&lt;br /&gt;
resource &amp;quot;kubernetes_deployment_v1&amp;quot; &amp;quot;my_deployment&amp;quot; {&lt;br /&gt;
  # ...&lt;br /&gt;
  spec {&lt;br /&gt;
    # ...&lt;br /&gt;
    template {&lt;br /&gt;
      # ...&lt;br /&gt;
      spec {&lt;br /&gt;
        container {&lt;br /&gt;
          # ...&lt;br /&gt;
&lt;br /&gt;
          security_context {&lt;br /&gt;
            run_as_user                = 1000&lt;br /&gt;
            run_as_non_root            = true&lt;br /&gt;
            allow_privilege_escalation = false&lt;br /&gt;
            seccomp_profile {&lt;br /&gt;
              type = &amp;quot;RuntimeDefault&amp;quot;&lt;br /&gt;
            }&lt;br /&gt;
            capabilities {&lt;br /&gt;
              drop = [&amp;quot;ALL&amp;quot;]&lt;br /&gt;
            }&lt;br /&gt;
          }&lt;br /&gt;
&lt;br /&gt;
          # ...&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[Terraform]] for more information.&lt;br /&gt;
&lt;br /&gt;
=== What is a CRD? ===&lt;br /&gt;
A [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/ Custom Resource Definition (CRD)], allows you to extend kubernetes capabilities and define custome resources. This is usually paired with an operator which reads the resources and performs some actions.&lt;br /&gt;
&lt;br /&gt;
We should never create our own, but third-party ones make it much easier for doing things such as creating ingress routes with traefik or define database clusters with our postgres operator.&lt;br /&gt;
&lt;br /&gt;
K9s and kubectl support these out of the box (as they are basically just schemas for yaml configuration), and you can see all pods by using the name of the resource.&lt;br /&gt;
&lt;br /&gt;
=== Traefik and gateways ===&lt;br /&gt;
&lt;br /&gt;
==== Cloudflare proxy ====&lt;br /&gt;
&lt;br /&gt;
=== Network Policies ===&lt;br /&gt;
&lt;br /&gt;
=== Helm ===&lt;br /&gt;
&lt;br /&gt;
=== Persistant Volumes ===&lt;br /&gt;
&lt;br /&gt;
=== Cronjobs ===&lt;br /&gt;
&lt;br /&gt;
=== K3S ===&lt;br /&gt;
&lt;br /&gt;
== Deployment with Tofu ==&lt;br /&gt;
&lt;br /&gt;
=== Retaining PVs ===&lt;br /&gt;
&lt;br /&gt;
=== Where is the state stored? ===&lt;br /&gt;
&lt;br /&gt;
== General Terraform management ==&lt;br /&gt;
&lt;br /&gt;
== K9s ==&lt;br /&gt;
&lt;br /&gt;
== Migrating storages ==&lt;br /&gt;
&lt;br /&gt;
== Multicluster setups ==&lt;br /&gt;
&lt;br /&gt;
=== Control plane ===&lt;br /&gt;
&lt;br /&gt;
=== How to manage Storage ===&lt;br /&gt;
&lt;br /&gt;
=== Safely shutting down nodes ===&lt;br /&gt;
&lt;br /&gt;
== Backups ==&lt;br /&gt;
&lt;br /&gt;
== Troubleshooting ==&lt;br /&gt;
&lt;br /&gt;
=== Kubernetes event log ===&lt;br /&gt;
&lt;br /&gt;
=== Traefik dashboard ===&lt;br /&gt;
&lt;br /&gt;
=== Shell-ing into pods ===&lt;br /&gt;
&lt;br /&gt;
=== K3S Log ===&lt;br /&gt;
&lt;br /&gt;
=== Emergency Debug pods ===&lt;br /&gt;
&lt;br /&gt;
=== Cloudflare API tokens ===&lt;br /&gt;
&lt;br /&gt;
=== SELinux ===&lt;br /&gt;
&lt;br /&gt;
=== Scenarios ===&lt;br /&gt;
&lt;br /&gt;
==== Deployment/statefulset created but pod not creating ====&lt;br /&gt;
&lt;br /&gt;
==== Pod in cash loop ====&lt;br /&gt;
&lt;br /&gt;
==== Pod cannot request any website ====&lt;br /&gt;
&lt;br /&gt;
==== Pod can access the internet but everything returns self-signed certificate ====&lt;br /&gt;
&lt;br /&gt;
==== Deployment can&#039;t access its database/valkey ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is requesting denied system privileges ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 404 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Pod is up, but I get a 403 when requesting visiting the page ====&lt;br /&gt;
&lt;br /&gt;
==== Authelia refuses to start up ====&lt;br /&gt;
&lt;br /&gt;
==== Node is crashing often after running out of RAM ====&lt;br /&gt;
&lt;br /&gt;
==== On IPV6 cluster and requests randomly timeout or return 404 ====&lt;br /&gt;
&lt;br /&gt;
==== Statefulset refusing to start pod (PVC) ====&lt;br /&gt;
&lt;br /&gt;
==== A node just crashed and went offline ====&lt;br /&gt;
&lt;br /&gt;
=== Cleaning up ===&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Cluster&amp;diff=127</id>
		<title>BOSS/Hosting/Cluster</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Cluster&amp;diff=127"/>
		<updated>2026-06-04T10:20:55Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Add information about BOSS&amp;#039;s kubernetes cluster&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;To make hosting for free as easy as pie, [[Bath Open Source Society|BOSS]] has its own [[Kubernetes]] cluster. This is hosted on servers donated to us by the Department of Computer Science and is hosted within their server room in 1W. This allows us to host databases and any docker container for free, which all is configured within [https://gitlab.bath.ac.uk/cs/int/terraform our terraform repo].&lt;br /&gt;
&lt;br /&gt;
== Deployment ==&lt;br /&gt;
If you are wanting to use this cluster for hosting projects, please get in contact with committee. However, this should be part of the process with adding a project.&lt;br /&gt;
&lt;br /&gt;
When deploying, a module and related configuration will be added to [https://gitlab.bath.ac.uk/cs/int/terraform our terraform repo], which then is deployed by a sysadmin to the staging cluster. Once we have validated that it works on staging, it will finally be deployed to production.&lt;br /&gt;
&lt;br /&gt;
== Machine Structure ==&lt;br /&gt;
{{Note|text=The more technical detail about the cluster is stored within [https://gitlab.bath.ac.uk/cs/boss/int-wiki BOSS&#039;s internal wiki in GitLab] and is only accessible by Committee and sysadmins. This page is here to provide some context and maybe provide some interesting information.}}&lt;br /&gt;
Due to me (hw2210) bricking an SSD in 6 months on my own 5 node cluster due to disk intensive operations with [https://longhorn.io/ longhorn] (a storage manager that shares drives across nodes), it was decided that we would only host a 1 node cluster with K3S&#039;s basic storage manager to extend the life of our finite disks as much as possible.&lt;br /&gt;
&lt;br /&gt;
From experience, it was also decided that we would host the clusters in VMs with immutable distro&#039;s to allow more flexibility with moving VMs between machines and stability (if it continuously crashes, we can get access to the console without plugging into host).&lt;br /&gt;
&lt;br /&gt;
Therefore our final structure was:&lt;br /&gt;
&lt;br /&gt;
* Production host machine running FreeBSD with ZFS (for data protection as ZFS is king)&lt;br /&gt;
** Production VM with CoreOS running K3S&lt;br /&gt;
* Staging host machine running FreeBSD with ZFS&lt;br /&gt;
** Staging VM with CoreOS running K3S&lt;br /&gt;
&lt;br /&gt;
The VM&#039;s themselves are then backed up to a backup server allowing for easy whole cluster revertions and recovery without any painful redeployment.&lt;br /&gt;
&lt;br /&gt;
=== CoreOS ===&lt;br /&gt;
CoreOS was chosen as it is an immutable distro based off of Fedora with [[SELinux]] support and is extremely lightweight. When first botting up, it uses an ignition file to configure itself (including network access), our ignition files can be found in [https://gitlab.bath.ac.uk/cs/int/ignition our ignition repo]. However, once the VM is running, it will not reapply ignition files, and so all changes must be performed by a wheel user.&lt;br /&gt;
&lt;br /&gt;
By immutable, we mean that the root directory cannot be altered, only &amp;lt;code&amp;gt;/var&amp;lt;/code&amp;gt; can be altered during the running of the distro. &amp;lt;code&amp;gt;/etc&amp;lt;/code&amp;gt; can also be changed, but it can also be reverted during boot. This has a few quirks, for example, home directories are found in &amp;lt;code&amp;gt;/var/home/&amp;lt;username&amp;gt;&amp;lt;/code&amp;gt; and installing any package requires a reboot of the VM - but you shouldn&#039;t really need to install any packages. Additionally, the package manager is now &amp;lt;code&amp;gt;rpm-ostree install ...&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Each alteration to the installed packages or &amp;lt;code&amp;gt;/etc&amp;lt;/code&amp;gt; creates a new version, by default, grub will choose the latest version to run, but during boot, there is the option to boot the previous version by using the down arrow.  If you are doing a dangerous update that might require you iterating through these versions, you can pin you current version before any updates, so you can go back to it if everything fails:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
sudo ostree admin pin 0&lt;br /&gt;
rpm-ostree status # See pinned versions&lt;br /&gt;
sudo ostree reset 2 # Reset back to the pinned version&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== FreeBSD ===&lt;br /&gt;
[[wikipedia:FreeBSD|FreeBSD]] was specifically chosen as it offers two benefits:&lt;br /&gt;
&lt;br /&gt;
* The best support for [[wikipedia:ZFS|ZFS]] on root (Linux doesn&#039;t like ZFSes license)&lt;br /&gt;
* Additional layer of security as something other than Linux (for context the decision was made around the time when we had many Linux vulnerabilities released).&lt;br /&gt;
&lt;br /&gt;
[[wikipedia:ZFS|ZFS]] was also chosen specifically because it has world-class data retention and backup tooling for RAID based systems, making it perfect for servers running with multiple large drives. We chose RAIDZ-1, which means one drive can fail and we can still recover all our data.&lt;br /&gt;
&lt;br /&gt;
FreeBSD is like Linux in many ways as it is Unix-like and subsequently many linux tools exist on FreeBSD and can be installed. However there are minor cultural differences, such as &amp;lt;code&amp;gt;sudo&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;systemd&amp;lt;/code&amp;gt; are disliked. Instead &amp;lt;code&amp;gt;doas&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;mdo&amp;lt;/code&amp;gt; is recommended. Additionally VMs/containers are called jails and have many more useful features.&lt;br /&gt;
&lt;br /&gt;
== K3S ==&lt;br /&gt;
[https://k3s.io K3S] is used for the kubernetes implementation as it provides a really easy stable base. Our configuration follows the [https://docs.k3s.io/security/hardening-guide hardened configuration] on a IPv4 only cluster (Bath sadly does not provide an IPv6 support) as well as running with [[SELinux]] enabled. This means that network policies must be defined to permit communication between pods (e.g. the database and the backend) and allows us blocking all outgoing traffic from a specific pod and requiring all pods do not run as root (with a few permitted exceptions).&lt;br /&gt;
&lt;br /&gt;
== Helper services ==&lt;br /&gt;
Kubernetes is hard, if you want more general knowledge about how to deploy and manage kubernetes cluster, please see the [[Kubernetes|Kubernetes page]]. To help with this, we also deploy a range of &amp;quot;helper&amp;quot; applications which help with deployment and add cool features to your own application:&lt;br /&gt;
&lt;br /&gt;
* [https://doc.traefik.io/traefik/ Traefik] - this is our http proxy software, managing routing traffic to the right application. This comes with the ability to [https://doc.traefik.io/traefik/expose/kubernetes/advanced/#create-middlewares create middlewares] to filter traffic based off a range of things, and combined with authelia, allows us to add a login page for applications which don&#039;t have one.&lt;br /&gt;
* [https://cert-manager.io/ Cert manager] - this managers automatically generating and renewing all our certificates, either DNS based, HTTP based or even Cloudflare Origin certificates, allowing for easy proxying with Cloudflare.&lt;br /&gt;
* [https://www.authelia.com/ Authelia] - our authentication manager hooked up to Bath&#039;s LDAP. This supports OpenConnect ID, allowing us to add authentication with bath credentials to more complex applications that require different groups. We have a range of unix groups which can be edited through [https://www.bath.ac.uk/services/group-manager/ Bath&#039;s group manager].&lt;br /&gt;
* [https://docker-mailserver.github.io/docker-mailserver/latest/ Docker mailserver] - This is configured with SPS, DKIM and DMARC to allow us to send emails under &amp;lt;code&amp;gt;bathcs.com&amp;lt;/code&amp;gt; for free and makes sure the emails are likely to actually reach the destination without going into spam.&lt;br /&gt;
* [https://cloudnative-pg.io/ Cloud native&#039;s Postgres Operator] - This is just a tool to easily deploy postgres databases within the cluster and manage them.&lt;br /&gt;
* [https://prometheus-operator.dev/ Prometheus operator] - This allows easy monitoring of the whole cluster and emails us if anything is going wrong&lt;br /&gt;
* [https://grafana.com/ Grafana] - cool graphs right?&lt;br /&gt;
* [https://github.com/lldap/lldap LLDAP] - This is an LDAP server written in Rust and allows us to easily generate application specific credentials for our different services.&lt;br /&gt;
* [https://docs.k3s.io/upgrades/automated K3S upgrades] - keeps our cluster up to date&lt;br /&gt;
* [https://k8up.io/ k8up] - Automatically backs up our critical PVs to a Scaleway S3 bucket.&lt;br /&gt;
* [https://github.com/kubernetes-sigs/security-profiles-operator Security Profile Operator] - Allows for managing SELinux policies within kubernetes resources. In most cases this should not need to be touched, and is quite brittle, but allows us to host applications which need access to the host machine itself (e.g. monitoring the node itself)&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Scaleway&amp;diff=126</id>
		<title>BOSS/Hosting/Scaleway</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Scaleway&amp;diff=126"/>
		<updated>2026-06-04T09:03:06Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Note|text=BOSS no longer actively uses Scaleway due to setting up [[BOSS/Hosting/Cluster|our own cluster]], this page is left here if you wish to host applications yourself|type=reminder}}&lt;br /&gt;
&lt;br /&gt;
[https://scaleway.com Scaleway] is similar to Google Cloud however it is a company based in Europe and has all the stuff we need to host our website for free. You can find the free tier listed on [https://www.scaleway.com/en/pricing/?tags=available scaleway’s pricing page], under “Serverless Containers”:&lt;br /&gt;
&lt;br /&gt;
* 400,000 GB-s per account per month&lt;br /&gt;
* 200,000 vCPU-s per account per month&lt;br /&gt;
* 75GB storage space in container registry (note that this &#039;&#039;&#039;MUST&#039;&#039;&#039; be public and if an image is bigger than 500MB it &#039;&#039;might&#039;&#039; produce charges).&lt;br /&gt;
&lt;br /&gt;
Furthermore if you do somehow go over this, it should not cost that much due to the low prices (and you can always see the estimated charge you will get at the end of the month).&lt;br /&gt;
&lt;br /&gt;
Scaleway’s documentation is also amazing, so if you need help just go [https://www.scaleway.com/en/docs/ their documentation].&lt;br /&gt;
&lt;br /&gt;
{{Note|text=You can have up to 1 instance running 24/7 with minimum memory and CPU under the free tier. However if you set the instance to 0 and use v2 of the sandboxing, it is as fast as if one instance is always running.}}&lt;br /&gt;
&lt;br /&gt;
== Signing up ==&lt;br /&gt;
For some reason, to sign up you only need to provide an email (and then specify a password and 2FA in the “profile”).&lt;br /&gt;
&lt;br /&gt;
You will also need access to a card which can be charged 1€ - we recommend using a virtual card which you can then assign £0 budget or deactivate after signing up so that we can pass to other people later on (and they can’t charge your account). Make sure if they do charge you, you can and will pay for it, asking BCSS for money if you have to.&lt;br /&gt;
&lt;br /&gt;
Once signed up, you have to verify the card (yes you haven’t done that yet). Simply click the button which says “verify card” and find the 4 digit code in your statement and enter that into the website.&lt;br /&gt;
&lt;br /&gt;
== Uploading a container ==&lt;br /&gt;
[https://youtu.be/zgxcCcKnXR0?feature=shared Scaleway Tutorial]    &lt;br /&gt;
&lt;br /&gt;
As mentioned in the video, to get a container running, you must upload it to the container registry.&lt;br /&gt;
&lt;br /&gt;
To do this, you will first have to create a namespace (if you have not created one already there should be a bit “create a namespace” button when you click on the container registry option in the sidebar).&lt;br /&gt;
{{Note|text=The free tier requires the images to be public and so set the namespace to be public as well which will reduce the likelihood of accidental costs. Remember there is also a 75GB limit.}}&lt;br /&gt;
You can either tag one of your previously built containers as they mention:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
podman tag ubuntu:latest rg.fr-par.scw.cloud/$namespace/ubuntu:latest&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;or you can build the dockerfile in the current directory with the tag&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
podman build -t rg.fr-par.scw.cloud/$namespace/ubuntu:latest .&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;And then you can push it with:&amp;lt;syntaxhighlight lang=&amp;quot;shell&amp;quot;&amp;gt;&lt;br /&gt;
podman push rg.fr-par.scw.cloud/$namespace/ubuntu:latest&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Now reload the page and you will be able to see your container there&lt;br /&gt;
&lt;br /&gt;
== Hosting a container ==&lt;br /&gt;
You can basically just click the 3 dots for the dropdown and click “deploy”.&lt;br /&gt;
&lt;br /&gt;
* You want to choose the namespace (scroll up)&lt;br /&gt;
* Set the port to the one your container is listening to (the one you exposed)&lt;br /&gt;
* Set the name + description&lt;br /&gt;
* For resources don’t go overboard as we want to basically set it to the lowest it will be able to run at but it preferably it should run fast.&lt;br /&gt;
* Scaling should be changed to between 0 and 1 instances.&lt;br /&gt;
* You will probably have environment variables and secrets to set, which is under “Advanced Options”.&lt;br /&gt;
* Under “Advanced Options” you will want to change the sandboxing to v2 as v1 is extremely slow at starting up instances.&lt;br /&gt;
* You can then ignore the estimated cost (if you’ve calculated your total VCPUs and memory is under on your ENTIRE account)&lt;br /&gt;
* Hit deploy!&lt;br /&gt;
&lt;br /&gt;
You will then have to add a custom endpoint. Note you will have to wait until the container has been fully deployed (which takes a while).&lt;br /&gt;
&lt;br /&gt;
Basically you want to add a &amp;lt;code&amp;gt;CNAME&amp;lt;/code&amp;gt; record to the DNS and set its value to the given domain (make sure Cloudflare proxy-ing is off). Once that is done, just click add endpoint and type in the hostname with the record and it’ll generate a certificate for it.&lt;br /&gt;
&lt;br /&gt;
== Updating a container ==&lt;br /&gt;
Okay so I’m not 100% sure this is the official method. But basically you can push your updated image and then go onto the container (in scaleway) and click on the deploy tab then just click on the button at the bottom which says “deploy” and it should redeploy like that.&lt;br /&gt;
&lt;br /&gt;
== Monitoring ==&lt;br /&gt;
Scaleway uses [[Grafana]] for monitoring which you can access once you have enabled Cockpit.&lt;br /&gt;
&lt;br /&gt;
We do love Grafana, if you don’t know how to use it, that’s fine, just follow the links scaleway gives you and marvel at Grafana’s beautiful and slightly slow interface (I feel like that’s because this is the free tier).&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Hosting&amp;diff=125</id>
		<title>BOSS/Hosting</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Hosting&amp;diff=125"/>
		<updated>2026-06-04T08:55:59Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Update links and information due to our recent cluster&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Our approach to hosting is that you will be able to host any low-intensity applications (this should encompass anything we are doing) for free. However, just so you do not get scammed, if any service shouts it from the roof top that their stuff is free, it’s probably a scam unless it’s a legitimate big business who can afford to do it.&lt;br /&gt;
&lt;br /&gt;
== Application structure ==&lt;br /&gt;
&lt;br /&gt;
The structure of your application is extremely important to be able to host stuff for free.&lt;br /&gt;
&lt;br /&gt;
You will want to split it up into three parts: frontend, backend and some sort of storage (e.g. database), this allows each part to be hosted separately and at lower costs. If you do not need a certain part, for example storage, you can just not host anything.&lt;br /&gt;
&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
If you can do it as a single static website, do it as a static website.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
For the frontend, this should always just be a static page. This can use any of the [[BOSS/Hosting/Pages|“Pages” products]], e.g. GitHub, GitLab, [[BOSS/Hosting/Cloudflare|Cloudflare Pages]] (which are all free). For BathCS, we use Cloudflare pages as we do not store our code on a GitLab instance that supports pages.&lt;br /&gt;
&lt;br /&gt;
By “static”, we mean that requesting a URL, it will always return the same JavaScript and HTML (this is a bit more of a loose definition than what it actually means as we can get dynamic features with an API).&lt;br /&gt;
&lt;br /&gt;
A consideration you may want to take is to integrate the frontend into the same system as the backend, which usually means you can host it with one docker container which means an easier deployment and allows for (easier) dynamic pages.&lt;br /&gt;
&lt;br /&gt;
However if you are using something like Svelte or React, it can be quite hard to get pages well optimised for loading.&lt;br /&gt;
&lt;br /&gt;
As a side note, it is becoming quite common nowadays to have authenticated areas (which require dynamic pages and access control) on a different website entirely (normally under a subdomain) e.g. &amp;lt;code&amp;gt;scaleway.com&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;console.scaleway.com&amp;lt;/code&amp;gt;. However, this is only recommended for big and complicated projects (which we want to stay away from).&lt;br /&gt;
&lt;br /&gt;
Wordpress, Wix or Squarespace are a big no-no.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
&lt;br /&gt;
A backend is usually only needed when you have some sort of storage and mostly acts as a validation layer and stops people from just deleting everything. Please avoid Python and TypeScript/JavaScript or other languages which don’t have runtime validation. This is for your own sanity as trying to implement the level of security which is required is a pain beyond compare.&lt;br /&gt;
&lt;br /&gt;
These should usually be using Docker containers or a serverless architecture (e.g. [[BOSS/Hosting/Cloudflare#Workers|Cloudflare Workers]]).&lt;br /&gt;
&lt;br /&gt;
==== Docker containers ====&lt;br /&gt;
&lt;br /&gt;
You should take a few extra things into consideration when using a Docker container:&lt;br /&gt;
&lt;br /&gt;
* If you are building a Docker container, please make sure it uses a small base image e.g. alpine (even if you have to install dependencies manually) as nearly all container registries will charge you for hosting large Docker images. If its a compiled language even better.&lt;br /&gt;
* Due to the usual lack of cache-able information, you want to be hosting as close to the UK as possible which also helps with GDPR stuff (so just don’t host anything in America – which invalidates many free hosting options sadly).&lt;br /&gt;
&lt;br /&gt;
You can host Docker containers for free on [[BOSS/Hosting/Scaleway|Scaleway]] however we host all ours on [[BOSS/Hosting/Cluster|our own kubernetes cluster]].&lt;br /&gt;
&lt;br /&gt;
=== Storage ===&lt;br /&gt;
&lt;br /&gt;
This one is the hardest to host for free, but there are free solutions out there if you just stick with the typical database structure (e.g. PostgreSQL).&lt;br /&gt;
&lt;br /&gt;
We do recommend PostgreSQL as it is the standard business practice nowadays, which we can host on [[BOSS/Hosting/Cluster|our own kubernetes cluster]].&lt;br /&gt;
&lt;br /&gt;
An alternative to this is MongoDB, whose Atlas program allows you to easily host this for free. However, this is discouraged (and outright shamed if used with TypeScript/JavaScript and Python) due to the issues with validating data before it goes into the database (as it literally just accepts objects).&lt;br /&gt;
&lt;br /&gt;
==== Required considerations ====&lt;br /&gt;
&lt;br /&gt;
* Hosting &#039;&#039;&#039;MUST&#039;&#039;&#039; be in the UK or Europe (due to GDPR)&lt;br /&gt;
* Please also consider how much data you need and if you are storing personal, identifiable information as that causes headaches with GDPR.&lt;br /&gt;
&lt;br /&gt;
In general just do a refresher course on GDPR.&lt;br /&gt;
&lt;br /&gt;
== How [[Bath Open Source Society|BOSS]] hosts stuff ==&lt;br /&gt;
Following try to follow this structure for all applications just to make management a bit easier and to have backup options. But all backends are hosted on our [[BOSS/Hosting/Cluster|own kubernetes cluster]] found in the university. This kubernetes cluster is hosted within an immutable VM on a server that we manage.&lt;br /&gt;
&lt;br /&gt;
This means that we are able to host (basically) anything we like for free as long as it fits within our resources budget.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Pages&amp;diff=124</id>
		<title>BOSS/Hosting/Pages</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Pages&amp;diff=124"/>
		<updated>2026-06-04T08:43:35Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;GitLab and GitHub both offer their “Pages” which can host static web pages (ones that can be built into raw HTML + JavaScript) for free.&lt;br /&gt;
&lt;br /&gt;
These also integrates with their build options, meaning as soon as you push to your git repo, it will build the project (if you are using a static site generator)&lt;br /&gt;
&lt;br /&gt;
== GitLab ==&lt;br /&gt;
&lt;br /&gt;
You can click on the “Deploy &amp;amp;gt; Pages” and go through the steps to create a &amp;lt;code&amp;gt;.gitlab-ci.yml&amp;lt;/code&amp;gt; file to automatically run &amp;lt;code&amp;gt;deno task build&amp;lt;/code&amp;gt; when you push to build your project.&lt;br /&gt;
&lt;br /&gt;
Once this is set up, you can follow the instructions on the interface to add a hostname. This will then display the page on that hostname.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Do not enable Cloudflare proxy for this (it won’t work).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
With this, you can have multiple repos with different hostnames.&lt;br /&gt;
&lt;br /&gt;
For more information see [https://docs.gitlab.com/ee/user/project/pages/ GitLab’s pages documentation].&lt;br /&gt;
&lt;br /&gt;
=== Bath GitLab ===&lt;br /&gt;
&lt;br /&gt;
Currently does not support pages sadly. We recommend to use [[BOSS/Hosting/Cloudflare#Pages|Cloudflare pages]] instead.&lt;br /&gt;
&lt;br /&gt;
== GitHub ==&lt;br /&gt;
&lt;br /&gt;
For more information see [https://pages.github.com/ GitHub’s pages documentation]&lt;br /&gt;
&lt;br /&gt;
We have not done much of this. But in most cases it requires you to add a CNAME record to DNS for &amp;lt;code&amp;gt;yourusername.github.io&amp;lt;/code&amp;gt; and then add a &amp;lt;code&amp;gt;CNAME&amp;lt;/code&amp;gt; file to the repo with your hostname.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Pages&amp;diff=123</id>
		<title>BOSS/Hosting/Pages</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Pages&amp;diff=123"/>
		<updated>2026-06-04T08:42:31Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;GitLab and GitHub both offer their “Pages” which can host static web pages (ones that can be built into raw HTML + JavaScript) for free.&lt;br /&gt;
&lt;br /&gt;
These also integrates with their build options, meaning as soon as you push to your git repo, it will build the project (if you are using a static site generator)&lt;br /&gt;
&lt;br /&gt;
== GitLab ==&lt;br /&gt;
&lt;br /&gt;
You can click on the “Deploy &amp;amp;gt; Pages” and go through the steps to create a &amp;lt;code&amp;gt;.gitlab-ci.yml&amp;lt;/code&amp;gt; file to automatically run &amp;lt;code&amp;gt;bun run build&amp;lt;/code&amp;gt; when you push to build your project.&lt;br /&gt;
&lt;br /&gt;
Once this is set up, you can follow the instructions on the interface to add a hostname. This will then display the page on that hostname.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Do not enable Cloudflare proxy for this (it won’t work).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
With this, you can have multiple repos with different hostnames.&lt;br /&gt;
&lt;br /&gt;
For more information see [https://docs.gitlab.com/ee/user/project/pages/ GitLab’s pages documentation]&lt;br /&gt;
&lt;br /&gt;
=== Bath GitLab ===&lt;br /&gt;
&lt;br /&gt;
Currently does not support pages sadly. We recommend to use [[BOSS/Hosting/Cloudflare#Pages|Cloudflare pages]] instead.&lt;br /&gt;
&lt;br /&gt;
== GitHub ==&lt;br /&gt;
&lt;br /&gt;
For more information see [https://pages.github.com/ GitHub’s pages documentation]&lt;br /&gt;
&lt;br /&gt;
We have not done much of this. But in most cases it requires you to add a CNAME record to DNS for &amp;lt;code&amp;gt;yourusername.github.io&amp;lt;/code&amp;gt; and then add a &amp;lt;code&amp;gt;CNAME&amp;lt;/code&amp;gt; file to the repo with your hostname.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Cloudflare&amp;diff=122</id>
		<title>BOSS/Hosting/Cloudflare</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Hosting/Cloudflare&amp;diff=122"/>
		<updated>2026-06-04T08:40:41Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Cloudflare offers free hosting (with unlimited access) using their “Pages” (for static pages) and “Workers” (for non-static pages).&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
If you are wanting to do this for &amp;lt;code&amp;gt;bathcs.com&amp;lt;/code&amp;gt; you need super-admin access to the BathCS account.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Pages ==&lt;br /&gt;
&lt;br /&gt;
We will be using Cloudflare Pages for all static pages (due to the university not allowing static page hosting on their GitLab instance due to security concerns).&lt;br /&gt;
&lt;br /&gt;
We integrate these into the CI (using secrets to make sure people cannot just access our account).&lt;br /&gt;
&lt;br /&gt;
We will explain the setup required for this to work, the Pages docs can be found on [https://developers.cloudflare.com/pages/ Cloudflare documentation on pages].&lt;br /&gt;
&lt;br /&gt;
=== Setup ===&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
A student with super-admin access to the Cloudflare account will have to perform these steps. This means a BathCS admin is needed.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
We will be directly uploading the output directory to Cloudflare, explained on [https://developers.cloudflare.com/pages/get-started/direct-upload/ Cloudflare’s direct upload documentation] and [https://developers.cloudflare.com/pages/how-to/use-direct-upload-with-continuous-integration/ continuous deployment documentation]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;You could add &amp;lt;code&amp;gt;wrangler&amp;lt;/code&amp;gt; as a development package, but there is usually no point and just using &amp;lt;code&amp;gt;deno&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;deno add npm:wrangler --dev --frozen=false&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;You will then have to add a project to Cloudflare. This can be done via the CLI or the dashboard (which will ask you to login to Cloudflare):&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;deno run --no-lock npm:wrangler pages project create&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Please make sure you set the production branch to &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt; – it will default to the current branch, which might be an issue later. See [https://www.geeksforgeeks.org/how-to-change-git-default-branch-from-master/ geeksforgeeks] for how to change this.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;You will then have to create a new API token for the BCSS account which is explained by [https://developers.cloudflare.com/pages/how-to/use-direct-upload-with-continuous-integration/#generate-an-api-token Cloudflare API token documentation]. Please make sure that “BCSS” is the only account included (and created with the shared Cloudflare account and not simply by a member of BCSS).&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
&amp;lt;p&amp;gt;Each website/project should have its own API key for security reasons&amp;lt;/p&amp;gt;&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;Please save this API token in the Admin organisation on VaultTub with its respective name (it will not be shown again).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Add the API token to the GitLab CI/CD variables (go to Settings &amp;amp;gt; CI/CD &amp;amp;gt; Variables).&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;There should be two variables added:&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;code&amp;gt;CLOUDFLARE_ACCOUNT_ID&amp;lt;/code&amp;gt;: Set to the BCSS account id&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;code&amp;gt;CLOUDFLARE_API_TOKEN&amp;lt;/code&amp;gt;: Set this to the API token generated&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;These should be marked as “Protected” and “Masked”&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;This is sadly less secure than a secrets manager, however we have yet to find a free one, so please limit Maintainer access to the project.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Update the pipeline to a build script&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;If you want some good example structures, we use [https://gitlab.bath.ac.uk/cs/components GitLab CI components] for standardised checks and deployment, please see the pipeline in [https://gitlab.bath.ac.uk/cs/services/froom/-/blob/dev/.gitlab-ci.yml?ref_type=heads froom] or [https://gitlab.bath.ac.uk/cs/services/wiki/-/blob/main/.gitlab-ci.yml?ref_type=heads our policies wiki] for examples of how to use them. Below is a raw example of how it works behind the hood (note building should be separated into its own pipeline)&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;yaml&amp;quot;&amp;gt;deploy:&lt;br /&gt;
  stage: deploy&lt;br /&gt;
  tags:&lt;br /&gt;
    - shared_runner&lt;br /&gt;
  before_script:&lt;br /&gt;
    - deno install&lt;br /&gt;
  script:&lt;br /&gt;
    - deno run build&lt;br /&gt;
    - deno run -A npm:wrangler pages deploy &amp;lt;out-dir&amp;gt; --project-name=&amp;lt;project-name&amp;gt;&lt;br /&gt;
  rules:&lt;br /&gt;
    - if: &#039;$CI_COMMIT_BRANCH == &amp;quot;$CI_DEFAULT_BRANCH&amp;quot;&lt;br /&gt;
      when: always&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;The production branch name should be the branch name you chose in part &amp;lt;code&amp;gt;2.&amp;lt;/code&amp;gt;, which hopefully should be &amp;lt;code&amp;gt;main&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Add custom domain for Cloudflare pages. Go to the Cloudflare dashboard and find the project in the “Workers &amp;amp;amp; Pages” section. And then select “Custom domains” and click “Set up a custom domain”. If you type a hostname on the domain “bathcs.com”, it should do everything for you 🎉.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Workers ==&lt;br /&gt;
&lt;br /&gt;
We have yet to implement Workers properly and so there is a summary of [https://developers.cloudflare.com/workers/ Cloudflare workers documentation]&lt;br /&gt;
&lt;br /&gt;
This has the downside that projects normally need to be written mostly from the ground up to work with workers (so it’s great if you are just about to start a new project).&lt;br /&gt;
&lt;br /&gt;
If you are going to write a project for Workers, we recommend checking out their [https://developers.cloudflare.com/workers/languages/rust/ Rust support] (and using that if you can) as JavaScript is still really bad for security.&lt;br /&gt;
&lt;br /&gt;
You may also want to design your project in a way that you can easily host it as a docker container (e.g. using &amp;lt;code&amp;gt;axum&amp;lt;/code&amp;gt; and refactoring significantly). This mostly just makes it easy for debugging and is a backup if Cloudflare decides to make Workers cost money.&lt;br /&gt;
&lt;br /&gt;
=== Resources ===&lt;br /&gt;
&lt;br /&gt;
* See [https://developers.cloudflare.com/workers/ Cloudflare documentation]&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Hosting&amp;diff=121</id>
		<title>BOSS/Hosting</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Hosting&amp;diff=121"/>
		<updated>2026-06-04T08:31:31Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Our approach to hosting is that you will be able to host any low-intensity applications (this should encompass anything we are doing) for free. However, just so you do not get scammed, if any service shouts it from the roof top that their stuff is free, it’s probably a scam unless it’s a legitimate big business who can afford to do it.&lt;br /&gt;
&lt;br /&gt;
== Application structure ==&lt;br /&gt;
&lt;br /&gt;
The structure of your application is extremely important to be able to host stuff for free.&lt;br /&gt;
&lt;br /&gt;
You will want to split it up into three parts: frontend, backend and some sort of storage (e.g. database), this allows each part to be hosted separately and at lower costs. If you do not need a certain part, for example storage, you can just not host anything.&lt;br /&gt;
&lt;br /&gt;
=== Frontend ===&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
If you can do it as a single static website, do it as a static website.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
For the frontend, this should always just be a static page. This can use any of the [[BOSS/Hosting/Pages|“Pages” products]], e.g. GitHub, GitLab, [[BOSS/Hosting/Cloudflare|Cloudflare Pages]] (which are all free). For BathCS, we use Cloudflare pages as we do not store our code on a GitLab instance that supports pages.&lt;br /&gt;
&lt;br /&gt;
By “static”, we mean that requesting a URL, it will always return the same JavaScript and HTML (this is a bit more of a loose definition than what it actually means as we can get dynamic features with an API).&lt;br /&gt;
&lt;br /&gt;
A consideration you may want to take is to integrate the frontend into the same system as the backend, which usually means you can host it with one docker container which means an easier deployment and allows for (easier) dynamic pages.&lt;br /&gt;
&lt;br /&gt;
However if you are using something like Svelte or React, it can be quite hard to get pages well optimised for loading.&lt;br /&gt;
&lt;br /&gt;
As a side note, it is becoming quite common nowadays to have authenticated areas (which require dynamic pages and access control) on a different website entirely (normally under a subdomain) e.g. &amp;lt;code&amp;gt;scaleway.com&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;console.scaleway.com&amp;lt;/code&amp;gt;. However, this is only recommended for big and complicated projects (which we want to stay away from).&lt;br /&gt;
&lt;br /&gt;
Wordpress, Wix or Squarespace are a big no-no.&lt;br /&gt;
&lt;br /&gt;
=== Backend ===&lt;br /&gt;
&lt;br /&gt;
A backend is usually only needed when you have some sort of storage and mostly acts as a validation layer and stops people from just deleting everything. Please avoid Python and TypeScript/JavaScript or other languages which don’t have runtime validation. This is for your own sanity as trying to implement the level of security which is required is a pain beyond compare.&lt;br /&gt;
&lt;br /&gt;
These should usually be using Docker containers or a serverless architecture (e.g. [[BOSS/Hosting/Cloudlfare#Workers|Cloudflare Workers]]).&lt;br /&gt;
&lt;br /&gt;
==== Docker containers ====&lt;br /&gt;
&lt;br /&gt;
You should take a few extra things into consideration when using a Docker container:&lt;br /&gt;
&lt;br /&gt;
* If you are building a Docker container, please make sure it uses a small base image e.g. alpine (even if you have to install dependencies manually) as nearly all container registries will charge you for hosting large Docker images. If its a compiled language even better.&lt;br /&gt;
* Due to the usual lack of cache-able information, you want to be hosting as close to the UK as possible which also helps with GDPR stuff (so just don’t host anything in America – which invalidates many free hosting sadly).&lt;br /&gt;
&lt;br /&gt;
You can host Docker containers for free on [[BOSS/Hosting/Scaleway|Scaleway]] (recommended) or [[BOSS/Hosting/Google Cloud|Google Cloud]]&lt;br /&gt;
&lt;br /&gt;
=== Storage ===&lt;br /&gt;
&lt;br /&gt;
This one is the hardest to host for free, but there are free solutions out there if you just stick with the typical database structure (e.g. PostgreSQL).&lt;br /&gt;
&lt;br /&gt;
We do recommend PostgreSQL as it is the standard business practice nowadays. However free hosting is much harder to find.&lt;br /&gt;
&lt;br /&gt;
An alternative to this is [[BOSS/Hosting/MongoDB Atlas|MongoDB]], whose Atlas program allows you to easily host this for free. However, this is discouraged (and outright shamed if used with TypeScript/JavaScript and Python) due to the issues with validating data before it goes into the database (as it literally just accepts objects).&lt;br /&gt;
&lt;br /&gt;
==== Required considerations ====&lt;br /&gt;
&lt;br /&gt;
* Hosting &#039;&#039;&#039;MUST&#039;&#039;&#039; be in the UK or Europe (due to GDPR)&lt;br /&gt;
* Please also consider how much data you need and if you are storing personal, identifiable information as that causes headaches with GDPR.&lt;br /&gt;
&lt;br /&gt;
In general just do a refresher course on GDPR.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Languages/TypeScript&amp;diff=120</id>
		<title>BOSS/Languages/TypeScript</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Languages/TypeScript&amp;diff=120"/>
		<updated>2026-06-04T08:28:29Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Again, please in most cases use TypeScript over JavaScript.&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
This should mostly be done by [https://biomejs.dev/ biome] or [https://prettier.io/ prettier] and [https://eslint.org/ eslint], but so you know&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Try to prioritise readability of code&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;2 spaces as tabs&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Lines should not be longer than 80 characters (install extension)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Trailing commas when the bracket on a new line&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;NO trailing whitespaces! (Install an extension to remove it for you)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Space after comments e.g. &amp;lt;code&amp;gt;// something&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Order imports alphabetically&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Prefer documentation over comments (see below on how you do that)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Comments should only be written when necessary, mainly to describe why something is done the way it is or to example a really unreadable bit of code (however this normally means you should change the code).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Methods should be ordered alphabetically (with private methods ordered separately and at the bottom of the file)&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;export function aFunc() {}&lt;br /&gt;
export function bFunc() {}&lt;br /&gt;
export function zFunc() {}&lt;br /&gt;
function aPrivateFunc() {}&lt;br /&gt;
function bPrivateFunc() {}&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These are really strict, if you take anything away remember this:&lt;br /&gt;
&lt;br /&gt;
* Use a formatter and linter: styling should be consistent throughout the project&lt;br /&gt;
* Readability of code takes precedent&lt;br /&gt;
&lt;br /&gt;
=== Use a Linter ===&lt;br /&gt;
&lt;br /&gt;
Linters usually will catch most issues, these can be installed as extensions to your preferred editor. E.g. for TypeScript we will be using [https://biomejs.dev/ biome] or [https://eslint.org/ ESLint].&lt;br /&gt;
&lt;br /&gt;
For Biome, both formatting and linting can be configured with &amp;lt;code&amp;gt;biome.json&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;&lt;br /&gt;
{&lt;br /&gt;
  &amp;quot;$schema&amp;quot;: &amp;quot;https://biomejs.dev/schemas/2.4.15/schema.json&amp;quot;,&lt;br /&gt;
  &amp;quot;vcs&amp;quot;: {&lt;br /&gt;
    &amp;quot;enabled&amp;quot;: true,&lt;br /&gt;
    &amp;quot;clientKind&amp;quot;: &amp;quot;git&amp;quot;,&lt;br /&gt;
    &amp;quot;useIgnoreFile&amp;quot;: true&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;files&amp;quot;: {&lt;br /&gt;
    &amp;quot;includes&amp;quot;: [&amp;quot;**&amp;quot;, &amp;quot;!!**/dist&amp;quot;]&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;formatter&amp;quot;: {&lt;br /&gt;
    &amp;quot;enabled&amp;quot;: true,&lt;br /&gt;
    &amp;quot;indentWidth&amp;quot;: 2,&lt;br /&gt;
    &amp;quot;indentStyle&amp;quot;: &amp;quot;space&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;linter&amp;quot;: {&lt;br /&gt;
    &amp;quot;enabled&amp;quot;: true,&lt;br /&gt;
    &amp;quot;rules&amp;quot;: {&lt;br /&gt;
      &amp;quot;recommended&amp;quot;: true&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;javascript&amp;quot;: {&lt;br /&gt;
    &amp;quot;formatter&amp;quot;: {&lt;br /&gt;
      &amp;quot;quoteStyle&amp;quot;: &amp;quot;double&amp;quot;&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;assist&amp;quot;: {&lt;br /&gt;
    &amp;quot;enabled&amp;quot;: true,&lt;br /&gt;
    &amp;quot;actions&amp;quot;: {&lt;br /&gt;
      &amp;quot;source&amp;quot;: {&lt;br /&gt;
        &amp;quot;organizeImports&amp;quot;: &amp;quot;on&amp;quot;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;css&amp;quot;: {&lt;br /&gt;
    &amp;quot;parser&amp;quot;: {&lt;br /&gt;
      &amp;quot;tailwindDirectives&amp;quot;: true&lt;br /&gt;
    }&lt;br /&gt;
  },&lt;br /&gt;
  &amp;quot;overrides&amp;quot;: [&lt;br /&gt;
    {&lt;br /&gt;
      &amp;quot;includes&amp;quot;: [&amp;quot;**/*.svelte&amp;quot;, &amp;quot;**/*.astro&amp;quot;, &amp;quot;**/*.vue&amp;quot;],&lt;br /&gt;
      &amp;quot;linter&amp;quot;: {&lt;br /&gt;
        &amp;quot;rules&amp;quot;: {&lt;br /&gt;
          &amp;quot;style&amp;quot;: {&lt;br /&gt;
            &amp;quot;useConst&amp;quot;: &amp;quot;off&amp;quot;,&lt;br /&gt;
            &amp;quot;useImportType&amp;quot;: &amp;quot;off&amp;quot;&lt;br /&gt;
          },&lt;br /&gt;
          &amp;quot;correctness&amp;quot;: {&lt;br /&gt;
            &amp;quot;noUnusedVariables&amp;quot;: &amp;quot;off&amp;quot;,&lt;br /&gt;
            &amp;quot;noUnusedImports&amp;quot;: &amp;quot;off&amp;quot;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
  ]&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Example ESLint config (&amp;lt;code&amp;gt;.estlintrc.cjs&amp;lt;/code&amp;gt;) which supports, react and typescript:&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
You will want to customise this to your projects needs. But this is a good base&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;import js from &amp;quot;@eslint/js&amp;quot;;&lt;br /&gt;
import prettier from &amp;quot;eslint-plugin-prettier&amp;quot;;&lt;br /&gt;
import tsParser from &amp;quot;@typescript-eslint/parser&amp;quot;;&lt;br /&gt;
import eslintJs from &amp;quot;@eslint/js&amp;quot;;&lt;br /&gt;
import eslintReact from &amp;quot;@eslint-react/eslint-plugin&amp;quot;;&lt;br /&gt;
import globals from &amp;quot;globals&amp;quot;;&lt;br /&gt;
import ts from &amp;quot;@typescript-eslint/eslint-plugin&amp;quot;;&lt;br /&gt;
&lt;br /&gt;
export default [&lt;br /&gt;
  {&lt;br /&gt;
    files: [&amp;quot;**/*.{ts,tsx,mjs,js,jsx}&amp;quot;, &amp;quot;astro.config.mjs&amp;quot;],&lt;br /&gt;
    languageOptions: {&lt;br /&gt;
      globals: { ...globals.browser },&lt;br /&gt;
      sourceType: &amp;quot;module&amp;quot;,&lt;br /&gt;
      ecmaVersion: 2022,&lt;br /&gt;
      parser: tsParser,&lt;br /&gt;
      parserOptions: {&lt;br /&gt;
        project: [&amp;quot;./tsconfig.json&amp;quot;],&lt;br /&gt;
        ecmaVersion: &amp;quot;latest&amp;quot;,&lt;br /&gt;
        sourceType: &amp;quot;module&amp;quot;,&lt;br /&gt;
        ecmaFeatures: {&lt;br /&gt;
          jsx: true, // Enable JSX syntax support&lt;br /&gt;
        },&lt;br /&gt;
      },&lt;br /&gt;
    },&lt;br /&gt;
    plugins: {&lt;br /&gt;
      eslintJs: eslintJs.configs.recommended,&lt;br /&gt;
      eslintReact: eslintReact.configs.recommended,&lt;br /&gt;
      prettier: prettier,&lt;br /&gt;
      &amp;quot;@typescript-eslint&amp;quot;: ts,&lt;br /&gt;
    },&lt;br /&gt;
    settings: {&lt;br /&gt;
      &amp;quot;mdx/code-blocks&amp;quot;: true,&lt;br /&gt;
    },&lt;br /&gt;
    rules: {&lt;br /&gt;
      &amp;quot;@typescript-eslint/triple-slash-reference&amp;quot;: &amp;quot;off&amp;quot;,&lt;br /&gt;
    },&lt;br /&gt;
  },&lt;br /&gt;
  {&lt;br /&gt;
    ...js.configs.recommended,&lt;br /&gt;
    ...ts.configs.recommendedTypeChecked,&lt;br /&gt;
    files: [&amp;quot;**/*.ts&amp;quot;, &amp;quot;**/*.tsx&amp;quot;],&lt;br /&gt;
    rules: {&lt;br /&gt;
      &amp;quot;@typescript-eslint/strict-boolean-expressions&amp;quot;: [&lt;br /&gt;
        2,&lt;br /&gt;
        {&lt;br /&gt;
          allowString: false,&lt;br /&gt;
          allowNumber: false,&lt;br /&gt;
        },&lt;br /&gt;
      ],&lt;br /&gt;
    },&lt;br /&gt;
  },&lt;br /&gt;
];&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Node that you must add the following dependencies:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;txt&amp;quot;&amp;gt;@eslint/js&lt;br /&gt;
eslint-plugin-prettier&lt;br /&gt;
@typescript-eslint/parser&lt;br /&gt;
@eslint-react/eslint-plugin&lt;br /&gt;
@typescript-eslint/eslint-plugin&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
For formatters, we recommend prettier with the following config:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;{&lt;br /&gt;
  &amp;quot;trailingComma&amp;quot;: &amp;quot;all&amp;quot;,&lt;br /&gt;
  &amp;quot;tabWidth&amp;quot;: 2,&lt;br /&gt;
  &amp;quot;semi&amp;quot;: true,&lt;br /&gt;
  &amp;quot;singleQuote&amp;quot;: false,&lt;br /&gt;
  &amp;quot;quoteProps&amp;quot;: &amp;quot;as-needed&amp;quot;,&lt;br /&gt;
  &amp;quot;jsxSingleQuote&amp;quot;: false,&lt;br /&gt;
  &amp;quot;bracketSpacing&amp;quot;: true,&lt;br /&gt;
  &amp;quot;bracketSameLine&amp;quot;: false,&lt;br /&gt;
  &amp;quot;arrowParens&amp;quot;: &amp;quot;always&amp;quot;,&lt;br /&gt;
  &amp;quot;printWidth&amp;quot;: 80,&lt;br /&gt;
  &amp;quot;useTabs&amp;quot;: false&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
(you will need to add &amp;lt;code&amp;gt;prettier&amp;lt;/code&amp;gt; as a dependency).&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
It is also worth noting that people have started to mention moving towards &amp;lt;code&amp;gt;biome&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;prettier&amp;lt;/code&amp;gt; (even the devs behind prettier), so you may want to use that instead&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
You can then add the following to your deno.jsonc:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;{&lt;br /&gt;
  &amp;quot;tasks&amp;quot;: {&lt;br /&gt;
    // Biome requires running /bin/sh which means it effectively wants all&lt;br /&gt;
    // permissions&lt;br /&gt;
    &amp;quot;biome&amp;quot;: &amp;quot;deno run -A npm:@biomejs/biome&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
    // ...&lt;br /&gt;
    &amp;quot;lint&amp;quot;: &amp;quot;deno task biome lint&amp;quot;,&lt;br /&gt;
    &amp;quot;lint:fix&amp;quot;: &amp;quot;deno task biome lint --write&amp;quot;,&lt;br /&gt;
    &amp;quot;format&amp;quot;: &amp;quot;deno task biome check --write&amp;quot;,&lt;br /&gt;
    &amp;quot;format:check&amp;quot;: &amp;quot;deno task biome check&amp;quot;,&lt;br /&gt;
    &lt;br /&gt;
    // With ESLINT + Prettier&lt;br /&gt;
    &amp;quot;eslint&amp;quot;: &amp;quot;deno run -A npm:eslint&amp;quot;,&lt;br /&gt;
    &amp;quot;prettier&amp;quot;: &amp;quot;deno run -A npm:prettier&amp;quot;,&lt;br /&gt;
    &amp;quot;prettier:files&amp;quot;: &amp;quot;deno task prettier &#039;src/**/*.{tsx,ts,md,mdx,json}&#039; &#039;*.{json,js,mjs,md}&#039; &#039;.prettierrc&#039;&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
    &amp;quot;lint&amp;quot;: &amp;quot;deno task eslint src --report-unused-disable-directives --max-warnings 0&amp;quot;,&lt;br /&gt;
    &amp;quot;lint:fix&amp;quot;: &amp;quot;deno task lint --fix&amp;quot;,&lt;br /&gt;
    &amp;quot;format&amp;quot;: &amp;quot;deno task prettier:files --write&amp;quot;,&lt;br /&gt;
    &amp;quot;format:check&amp;quot;: &amp;quot;deno task prettier:files --check&amp;quot;&lt;br /&gt;
  },&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Specifically ==&lt;br /&gt;
&lt;br /&gt;
These are normally the typical styling recommendations for JavaScript:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;camelCase&amp;lt;/code&amp;gt; for variables + functions. Preferably, if you had an acronym in the name, all letters should be the same case e.g. &amp;lt;code&amp;gt;myNPC&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;npcPair&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;PascalCase&amp;lt;/code&amp;gt; for classes&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;SCREAMING_CASE&amp;lt;/code&amp;gt; for global constants&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Use &amp;lt;code&amp;gt;const&amp;lt;/code&amp;gt; by default. Use &amp;lt;code&amp;gt;let&amp;lt;/code&amp;gt; only if you need to reassign to the variable.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Constants in JavaScript + TypeScript are not actually constant, they just can’t be reassigned.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Reduce use of &amp;lt;code&amp;gt;any&amp;lt;/code&amp;gt;. Unfortunately TypeScript can be quite dumb at times so this can be used for type conversion.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Prefer &amp;lt;code&amp;gt;&amp;amp;quot;&amp;lt;/code&amp;gt; over &amp;lt;code&amp;gt;&#039;&amp;lt;/code&amp;gt; for strings, unless you have &amp;lt;code&amp;gt;&amp;amp;quot;&amp;lt;/code&amp;gt; inside the string&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;This one is more personal so not necessary (consistency though!)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Starting a new scope with &amp;lt;code&amp;gt;{&amp;lt;/code&amp;gt; should be on the same line, not on a new line, e.g. in javascript:&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;function something(my_arg) {&lt;br /&gt;
    ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// NOT&lt;br /&gt;
function something(my_arg)&lt;br /&gt;
{&lt;br /&gt;
    ...&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;else&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;else if&amp;lt;/code&amp;gt; statements should be on the same line as the scope (ignore the lecturers’ preferences for this).&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;js&amp;quot;&amp;gt;if (myVar === &#039;Something&#039;) {&lt;br /&gt;
  ...&lt;br /&gt;
} else if (myOtherVar !== &#039;THING&#039;) {&lt;br /&gt;
  ...&lt;br /&gt;
} else {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// NOT&lt;br /&gt;
if (myVar === &#039;Something&#039;) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
else if (myOtherVar !== &#039;THING&#039;) {&lt;br /&gt;
  ...&lt;br /&gt;
}&lt;br /&gt;
else {&lt;br /&gt;
  ...&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Prefer creating your own interface over using the &amp;lt;code&amp;gt;object&amp;lt;/code&amp;gt; keyword&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;ESLint basically covers all my other issues&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Documentation ==&lt;br /&gt;
&lt;br /&gt;
TypeScript and JavaScript have actually good documentation (probably the only good thing about them), so please use it.&lt;br /&gt;
&lt;br /&gt;
Basically please read through [https://gamedevacademy.org/javascript-docstrings-tutorial/ this].&lt;br /&gt;
&lt;br /&gt;
== Package manager ==&lt;br /&gt;
As an asside, as already alluded to, [[Bath Open Source Society|BOSS]] has a standard of using [https://deno.com/ deno] for its package manage and runtime due to its significant focus on security. However, this comes with some learning barriers as it unlike your regular npm, yarn or bun and goes further than pnpm.&lt;br /&gt;
&lt;br /&gt;
Our configuration of deno is as follows, which can be found in the &amp;lt;code&amp;gt;deno.jsonc&amp;lt;/code&amp;gt; of any project:&amp;lt;syntaxhighlight lang=&amp;quot;json&amp;quot;&amp;gt;{&lt;br /&gt;
  &amp;quot;$schema&amp;quot;: &amp;quot;https://raw.githubusercontent.com/denoland/deno/refs/tags/v2.8.0/cli/schemas/config-file.v1.json&amp;quot;,&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;tasks&amp;quot;: {&lt;br /&gt;
    // ... Any &amp;quot;scripts&amp;quot; from package.json&lt;br /&gt;
  },&lt;br /&gt;
&lt;br /&gt;
  &amp;quot;minimumDependencyAge&amp;quot;: &amp;quot;P2D&amp;quot;,&lt;br /&gt;
  &amp;quot;vendor&amp;quot;: true,&lt;br /&gt;
  &amp;quot;lock&amp;quot;: { &amp;quot;frozen&amp;quot;: true }&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;What this configuration does is as follows:&lt;br /&gt;
&lt;br /&gt;
* When updating, the chosen dependency verion must be older than 2 days to help mitigate from supply chain attacks&lt;br /&gt;
* By default, the lock file is frozen, and so when adding or updating packages, you must explicity include &amp;lt;code&amp;gt;--frozen=false&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;vendor&amp;lt;/code&amp;gt; folder is used as a local cache for remote modules, decreasing overall disk usage.&lt;br /&gt;
&lt;br /&gt;
=== Core differences with npm ===&lt;br /&gt;
&lt;br /&gt;
* Added packages must include a namespace, e.g. anything from &amp;lt;code&amp;gt;npm&amp;lt;/code&amp;gt; (basically anything) should be prefixed with &amp;lt;code&amp;gt;npm:&amp;lt;/code&amp;gt;. You can also install from the [https://jsr.io/ JSR] by prefixing with &amp;lt;code&amp;gt;jsr:&amp;lt;/code&amp;gt;&lt;br /&gt;
* Scripts are not found in &amp;lt;code&amp;gt;package.json&amp;lt;/code&amp;gt; instead they should be in deno&#039;s config: &amp;lt;code&amp;gt;deno.jsonc&amp;lt;/code&amp;gt; under &amp;lt;code&amp;gt;tasks&amp;lt;/code&amp;gt;&lt;br /&gt;
* Running &amp;quot;tasks&amp;quot; should be done through deno&#039;s task cmd: &amp;lt;code&amp;gt;deno task ...&amp;lt;/code&amp;gt;&lt;br /&gt;
* Running package scripts, the full package name must be used e.g. &amp;lt;code&amp;gt;deno run -A npm:@biomejs/biome&amp;lt;/code&amp;gt;&lt;br /&gt;
* You must permit applications to use features such as environmental variables or reading your directories. If deploying yourself, it will ask for each permission. You can also permit anything by using &amp;lt;code&amp;gt;deno run -A&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Languages/Rust&amp;diff=119</id>
		<title>BOSS/Languages/Rust</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Languages/Rust&amp;diff=119"/>
		<updated>2026-06-04T08:02:42Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The rust compiler is pretty brutal so not much has to go in here.&lt;br /&gt;
&lt;br /&gt;
* Change the default check command in VSCode (instead of check use &amp;lt;code&amp;gt;clippy&amp;lt;/code&amp;gt;)&lt;br /&gt;
* 4 spaces for tabs&lt;br /&gt;
* Macros are cool but don’t overuse them.&lt;br /&gt;
&lt;br /&gt;
== Use rust analyzer + clippy ==&lt;br /&gt;
&lt;br /&gt;
Rust analyzer is great and has a bunch of recommendations which you should follow, so set this up and use it.&lt;br /&gt;
&lt;br /&gt;
== Use rustfmt ==&lt;br /&gt;
&lt;br /&gt;
This just standardises the code format.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Languages/Python&amp;diff=118</id>
		<title>BOSS/Languages/Python</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Languages/Python&amp;diff=118"/>
		<updated>2026-06-04T08:01:46Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Should follow [https://peps.python.org/pep-0008/ PEP-8]&lt;br /&gt;
&lt;br /&gt;
== Quick overview ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;4 spaces as tabs&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;snake_case&amp;lt;/code&amp;gt; for variables, functions and modules&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&amp;lt;code&amp;gt;PascalCase&amp;lt;/code&amp;gt; for classes&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Global variables should not be used.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Global constants should be in &amp;lt;code&amp;gt;SCREAMING_CASE&amp;lt;/code&amp;gt; when used.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Lines should not be longer than 80 characters&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Use trailing commas when the closing bracket is on a new line&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Space after comments e.g. &amp;lt;code&amp;gt;# something&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Protected methods and attributes to start with &amp;lt;code&amp;gt;_&amp;lt;/code&amp;gt;, private to start with &amp;lt;code&amp;gt;__&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;When a variable is not used, give it the name &amp;lt;code&amp;gt;_&amp;lt;/code&amp;gt; e.g.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;for _ in range(10):&lt;br /&gt;
    print(&amp;quot;Hi&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
(important_var, _) = some_func()&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Non-biased opinions ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Try to prioritise readability of code&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Order imports alphabetically but split internal imports&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Comments should only be written when necessary, mainly to describe why something is done the way it is or to example a really unreadable bit of code (however this normally means you should change the code).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Methods should be ordered alphabetically (with private methods ordered separately and at the bottom of the file) e.g.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;def a_func():&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
def b_func():&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
def z_func():&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
def _a_private_func():&lt;br /&gt;
  ...&lt;br /&gt;
&lt;br /&gt;
def _b_private_func():&lt;br /&gt;
  ...&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Please use statically-typed code when possible: always for functions and parameters and then only for variables when its hard to tell e.g.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;MY_CONST: dict[str, set[list[(str|int)]]] = {&lt;br /&gt;
    &amp;quot;something&amp;quot;: set([&amp;quot;hi&amp;quot;], [2], [3]),&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
def my_func(param1: int, param2: list[str]) -&amp;gt; (dict[str, int]|None):&lt;br /&gt;
    my_variable = &amp;quot;hi&amp;quot;&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
def void_func():&lt;br /&gt;
    ...&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;However note that we should try to avoid using the &amp;lt;code&amp;gt;str|int&amp;lt;/code&amp;gt; type and keep to one type when possible.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Generators are cool but don’t over use them and format them to be more readable with good naming schemes. e.g.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;py&amp;quot;&amp;gt;cube = np.from_iter((&lt;br /&gt;
    (y + x for x in range(10))&lt;br /&gt;
    for y in range(10)&lt;br /&gt;
), ...)&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using Pylint ==&lt;br /&gt;
&lt;br /&gt;
On most IDEs (including vscode) you should be able to get a pylint extension to point out mistakes and annoy you into writing okay code. Please use this as it should help you with the above styling requirements.&lt;br /&gt;
&lt;br /&gt;
You can also use flake8 (however these are really strict and I don’t like some of them), so here is an example configuration (in the &amp;lt;code&amp;gt;.flake8&amp;lt;/code&amp;gt; file):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre class=&amp;quot;toml&amp;quot;&amp;gt;[flake8]&lt;br /&gt;
extend-ignore = E272,E221,E227,E201,E202&lt;br /&gt;
exclude = .git,__pycache__&lt;br /&gt;
max-complexity = 10&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Documentation ==&lt;br /&gt;
&lt;br /&gt;
There is annoyingly no exact style in python for these… Here is something I kinda like.&lt;br /&gt;
&lt;br /&gt;
Documentation should be for all public modules, methods, classes and functions (however please also try to do them for private versions, but they don’t have to be as indepth).&lt;br /&gt;
&lt;br /&gt;
The style of documentation is kinda whatever you want it to be, so here is a somewhat okay layout. (Any better styles are most welcome as long as we are consistent)&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
example_module.py&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;python&amp;quot;&amp;gt;&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
Description&lt;br /&gt;
&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
def my_func(param1: int) -&amp;gt; str:&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;Description of return which fits in less than 80 chars&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    ...&lt;br /&gt;
&lt;br /&gt;
def my_complex_func(param1: list[str]):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    Description&lt;br /&gt;
&lt;br /&gt;
    Parameters&lt;br /&gt;
    ----------&lt;br /&gt;
    param1 (type): Description&lt;br /&gt;
&lt;br /&gt;
    Return&lt;br /&gt;
    ------&lt;br /&gt;
    brief_name (type): Description&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
class MyClass:&lt;br /&gt;
  &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  Description&lt;br /&gt;
&lt;br /&gt;
  Attributes&lt;br /&gt;
  ----------&lt;br /&gt;
  ...&lt;br /&gt;
  &amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  def my_method():&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;...&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
  ...&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Languages/Markdown&amp;diff=117</id>
		<title>BOSS/Languages/Markdown</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Languages/Markdown&amp;diff=117"/>
		<updated>2026-06-04T08:00:55Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This is not a programming language, but all READMEs should be written in markdown, and this whole wiki is.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Tab size to 2 spaces&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Use &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt; over &amp;lt;code&amp;gt;*&amp;lt;/code&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Max line length should be 80&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Use blank lines to space things out!&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;## A title&lt;br /&gt;
&lt;br /&gt;
Some text&lt;br /&gt;
&lt;br /&gt;
- point1&lt;br /&gt;
- point2&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Code blocks should always have a filetype&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;```py&lt;br /&gt;
print(&amp;amp;quot;Hi&amp;amp;quot;)&lt;br /&gt;
```&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Anything relating to the point above should be indented and blank space left&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;pre class=&amp;quot;md&amp;quot;&amp;gt;- point1&lt;br /&gt;
&lt;br /&gt;
  some other paragraph&lt;br /&gt;
&lt;br /&gt;
- point2&amp;lt;/pre&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Formatters do exist (e.g. [https://prettier.io/ prettier] or [https://biomejs.dev/ biome]) so use them please!&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Languages&amp;diff=116</id>
		<title>BOSS/Languages</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Languages&amp;diff=116"/>
		<updated>2026-06-04T07:59:21Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Minor workind update&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;As this is for students, we want to allow students to use whatever language they feel most comfortable with, especially due to our security and code quality standards.&lt;br /&gt;
&lt;br /&gt;
However having said that we want a consistent style throughout the projects (which is quite difficult with so many people working on that). So for each language we have used in the past we have written up some code quality guidelines.&lt;br /&gt;
&lt;br /&gt;
If you are going to write in a new language, please follow the general code quality guidelines below.&lt;br /&gt;
&lt;br /&gt;
== Recommendations ==&lt;br /&gt;
&lt;br /&gt;
And now as any pedantic programmer, I will give my recommendations for you to be using.&lt;br /&gt;
&lt;br /&gt;
=== Static Sites ===&lt;br /&gt;
&lt;br /&gt;
For static sites we ask that you don’t use a programming language and instead use a static site generator like [https://gohugo.io/ hugo] and write the pages in markdown.&lt;br /&gt;
&lt;br /&gt;
This is just to help the simplicity of the page and make it much easier to edit for non-technical people, while also meaning we don’t use Wordpress (it’s insecure).&lt;br /&gt;
&lt;br /&gt;
Some static site generators:&lt;br /&gt;
&lt;br /&gt;
* [https://astro.build/ astro]&lt;br /&gt;
* [https://gohugo.io/ hugo]&lt;br /&gt;
* [https://www.getzola.org/ zola]&lt;br /&gt;
&lt;br /&gt;
=== Other Projects ===&lt;br /&gt;
&lt;br /&gt;
We recommend using a language which suites the purposes of your project. As most projects will be websites, we recommend using a language with a good library for this. These include:&lt;br /&gt;
&lt;br /&gt;
* Rust (using [https://rocket.rs/ rocket])&lt;br /&gt;
* Go (using [https://github.com/gorilla/mux gorilla/mux])&lt;br /&gt;
* Zig&lt;br /&gt;
* Any other sane type safe language&lt;br /&gt;
&lt;br /&gt;
Languages and combos that are not good for this purpose, due to the lack of type safety or insecurity:&lt;br /&gt;
&lt;br /&gt;
* TypeScript (unless its just for the frontend or utilising a package which performs runtime type validation)&lt;br /&gt;
* Python&lt;br /&gt;
* Perl&lt;br /&gt;
* JavaScript (use TypeScript instead for our sanity) - this does not include small scripts for pages.&lt;br /&gt;
* Ruby (does not implement some parts of http request spec correctly)&lt;br /&gt;
* PHP (due to being run as root in most circumstances, this has significant security vulnerabilities). If you don’t know what this means, look up web shells, almost all of them are PHP so it’s better just not to even have the possibility of being compatible with them.&lt;br /&gt;
* Any esoteric languages (I feel like they all probably don’t even support this)&lt;br /&gt;
* Java (it sucks, use Kotlin instead at least)&lt;br /&gt;
* C/C++ (they are not great for webservers): use Zig or Rust instead please&lt;br /&gt;
&lt;br /&gt;
Please also choose a language which another student may be able to read in 5 years time.&lt;br /&gt;
&lt;br /&gt;
== General Guidelines ==&lt;br /&gt;
&lt;br /&gt;
As we don’t have requirements on what language you should be using, we have some general code quality guidelines.&lt;br /&gt;
&lt;br /&gt;
However for the languages we have projects in we have written more specific code guidelines.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;All functions should be documented (even if it’s just one sentence explaining what it does) using the official documentation standard for a given language e.g. Docstrings for Python, JSDoc for JavaScript + TypeScript etc.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;For more complex functions, more descriptive documentation should be included&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Prioritise &#039;&#039;&#039;readability&#039;&#039;&#039; over thousands of comments&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Follow language styles and use a linter. For example, in Python follow [https://peps.python.org/pep-0008/ PEP8 standards] with &amp;lt;code&amp;gt;snake_case&amp;lt;/code&amp;gt; variable names, in JavaScript and TypeScript follow [https://eslint.org/ ESLint]/[https://biomejs.dev/ Biome] with &amp;lt;code&amp;gt;camelCase&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Please make use of modules + splitting code up into multiple files (sensibly)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&#039;&#039;&#039;NO TRAILING WHITESPACE!&#039;&#039;&#039; Install an extension to auto remove it please!&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Use spaces over tabs (this ensures alignment is correct between machines)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Trailing commas when array or parameters on new lines for all languages which support it (if the language doesn’t support it please question your choice of language). E.g.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;py&amp;quot;&amp;gt;def my_func(&lt;br /&gt;
  param1: int,&lt;br /&gt;
  param2: list[str],&lt;br /&gt;
) -&amp;gt; list[int]:&lt;br /&gt;
  return [&lt;br /&gt;
    1,&lt;br /&gt;
    2,&lt;br /&gt;
    3,&lt;br /&gt;
    4,&lt;br /&gt;
  ]&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Trailing commas when the bracket on a new line&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So basically in summary write documentation and follow the language’s style!&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Languages&amp;diff=115</id>
		<title>BOSS/Languages</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Languages&amp;diff=115"/>
		<updated>2026-06-04T07:57:50Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com and update&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;As this is for students, we want to allow students to use whatever language they feel most comfortable with, especially due to our security and code quality standards.&lt;br /&gt;
&lt;br /&gt;
However having said that we want a consistent style throughout the projects (which is quite difficult with so many people working on that). So for each language we have used in the past we have written up some code quality guidelines.&lt;br /&gt;
&lt;br /&gt;
If you are going to write in a new language, please follow the general code quality guidelines below.&lt;br /&gt;
&lt;br /&gt;
== Recommendations ==&lt;br /&gt;
&lt;br /&gt;
And now as any pedantic programmer, I will give my recommendations for you to be using.&lt;br /&gt;
&lt;br /&gt;
=== Static Sites ===&lt;br /&gt;
&lt;br /&gt;
For static sites we ask that you don’t use a programming language and instead use a static site generator like [https://gohugo.io/ hugo] and write the pages in markdown.&lt;br /&gt;
&lt;br /&gt;
This is just to help the simplicity of the page and make it much easier to edit for non-technical people, while also meaning we don’t use Wordpress (it’s insecure).&lt;br /&gt;
&lt;br /&gt;
Some static site generators:&lt;br /&gt;
&lt;br /&gt;
* [https://astro.build/ astro]&lt;br /&gt;
* [https://gohugo.io/ hugo]&lt;br /&gt;
* [https://www.getzola.org/ zola]&lt;br /&gt;
&lt;br /&gt;
=== Other Projects ===&lt;br /&gt;
&lt;br /&gt;
We recommend using a language which suites the purposes of your project. As most projects will be websites, we recommend using a language with a good library for this. These include:&lt;br /&gt;
&lt;br /&gt;
* Rust (using [https://rocket.rs/ rocket])&lt;br /&gt;
* Go (using [https://github.com/gorilla/mux gorilla/mux])&lt;br /&gt;
* Zig&lt;br /&gt;
&lt;br /&gt;
Languages and combos that are not good for this purpose, due to the lack of type safety or insecurity:&lt;br /&gt;
&lt;br /&gt;
* TypeScript (unless its just for the frontend or utilising a package which performs runtime type validation)&lt;br /&gt;
* Python&lt;br /&gt;
* Perl&lt;br /&gt;
* JavaScript (use TypeScript instead for our sanity) - this does not include small scripts for pages.&lt;br /&gt;
* Ruby (does not implement some parts of http request spec correctly)&lt;br /&gt;
* PHP (due to being run as root in most circumstances, this has significant security vulnerabilities). If you don’t know what this means, look up web shells, almost all of them are PHP so it’s better just not to even have the possibility of being compatible with them.&lt;br /&gt;
* Any esoteric languages (I feel like they all probably don’t even support this)&lt;br /&gt;
* Java (it sucks, use Kotlin instead at least)&lt;br /&gt;
* C/C++ (they are not great for webservers): use Zig or Rust instead please&lt;br /&gt;
&lt;br /&gt;
Please also choose a language which another student may be able to read in 5 years time.&lt;br /&gt;
&lt;br /&gt;
== General Guidelines ==&lt;br /&gt;
&lt;br /&gt;
As we don’t have requirements on what language you should be using, we have some general code quality guidelines.&lt;br /&gt;
&lt;br /&gt;
However for the languages we have projects in we have written more specific code guidelines.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;All functions should be documented (even if it’s just one sentence explaining what it does) using the official documentation standard for a given language e.g. Docstrings for Python, JSDoc for JavaScript + TypeScript etc.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;For more complex functions, more descriptive documentation should be included&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Prioritise &#039;&#039;&#039;readability&#039;&#039;&#039; over thousands of comments&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Follow language styles and use a linter. For example, in Python follow [https://peps.python.org/pep-0008/ PEP8 standards] with &amp;lt;code&amp;gt;snake_case&amp;lt;/code&amp;gt; variable names, in JavaScript and TypeScript follow [https://eslint.org/ ESLint]/[https://biomejs.dev/ Biome] with &amp;lt;code&amp;gt;camelCase&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Please make use of modules + splitting code up into multiple files (sensibly)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&#039;&#039;&#039;NO TRAILING WHITESPACE!&#039;&#039;&#039; Install an extension to auto remove it please!&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Use spaces over tabs (this ensures alignment is correct between machines)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Trailing commas when array or parameters on new lines for all languages which support it (if the language doesn’t support it please question your choice of language). E.g.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;py&amp;quot;&amp;gt;def my_func(&lt;br /&gt;
  param1: int,&lt;br /&gt;
  param2: list[str],&lt;br /&gt;
) -&amp;gt; list[int]:&lt;br /&gt;
  return [&lt;br /&gt;
    1,&lt;br /&gt;
    2,&lt;br /&gt;
    3,&lt;br /&gt;
    4,&lt;br /&gt;
  ]&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Trailing commas when the bracket on a new line&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So basically in summary write documentation and follow the language’s style!&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Design&amp;diff=114</id>
		<title>BOSS/Design</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Design&amp;diff=114"/>
		<updated>2026-06-04T07:49:48Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Rename hosting page&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;We don’t want to specify any harsh requirements that would prevent students creating a variety of projects.&lt;br /&gt;
&lt;br /&gt;
However, we do need to give some recommendations for designing projects, so that future students can still understand and maintain the projects.&lt;br /&gt;
&lt;br /&gt;
It is also worth noting that due to the requirement of hosting things for free, you will want to read the [[BOSS/Hosting|hosting page]] as this limits what you can do signficantly.&lt;br /&gt;
&lt;br /&gt;
Please make sure to follow the [[BOSS/Languages|coding guidelines]] as well for any programming.&lt;br /&gt;
&lt;br /&gt;
== KISS ==&lt;br /&gt;
&lt;br /&gt;
Keep it simple stupid!&lt;br /&gt;
&lt;br /&gt;
If it doesn’t require a feature, don’t add it.&lt;br /&gt;
&lt;br /&gt;
If it doesn’t require being written in a fancy programming language, don’t use that.&lt;br /&gt;
&lt;br /&gt;
If it doesn’t require data collection, don’t collect people’s data!&lt;br /&gt;
&lt;br /&gt;
== Existing Systems ==&lt;br /&gt;
&lt;br /&gt;
If a system exists, please try to use it unless its completely impractical.&lt;br /&gt;
&lt;br /&gt;
This is because students don’t have much time and don’t want to be managing a completely new system. However please use open source projects (closed source nearly always comes with a cost, either data collection or money both of which we cannot afford)&lt;br /&gt;
&lt;br /&gt;
Note: I’m mostly referring to libraries. I think its quite fun to write simple projects like a link shortener for yourself.&lt;br /&gt;
&lt;br /&gt;
=== Licensing ===&lt;br /&gt;
&lt;br /&gt;
Please see [https://boss.bathcs.com/policies/licenses/|our license policy] for information on how our projects are licensed.&lt;br /&gt;
&lt;br /&gt;
In general, new projects should be licensed under [https://spdx.org/licenses/LLVM-exception.html &amp;lt;code&amp;gt;Apache-2.0 WITH LLVM-Exception&amp;lt;/code&amp;gt;], contributors must agree to the [https://developercertificate.org/ Developers Certificate of Origin (DCO)], and any contributions must not violate the [https://www.bath.ac.uk/publications/university-of-bath-ip-policy/ University of Bath’s Intellectual Property policy].&lt;br /&gt;
&lt;br /&gt;
== Keep it general ==&lt;br /&gt;
&lt;br /&gt;
Always try to keep the structure of the project general, so that it can be repurposed easily. Don’t take it too far though—there’s a balance.&lt;br /&gt;
&lt;br /&gt;
Generalisation normally leads to cleaner code and better maintainability.&lt;br /&gt;
&lt;br /&gt;
== Modules ==&lt;br /&gt;
&lt;br /&gt;
Modules are your friend, different languages implement them differently and have different ideas about how they should be organised (e.g. Go vs Rust).&lt;br /&gt;
&lt;br /&gt;
We don’t really have opinions, as you should use the style which fits the programming language you are using. If you don’t like that style, don’t write a project in that language (for maintainers, I’m sorry but deal with it).&lt;br /&gt;
&lt;br /&gt;
If you try to go against the style of the programming language, you will find more barriers and have to implement hacky workarounds which will lead to unreadable, confusing and buggy code.&lt;br /&gt;
&lt;br /&gt;
== Data storage ==&lt;br /&gt;
&lt;br /&gt;
Due to limitations with the fact we will never get any funding, we must create projects which only relies on database storage (which we can host for free, see the [[BOSS/Projects/Database|database page]]).&lt;br /&gt;
&lt;br /&gt;
== Docker ==&lt;br /&gt;
&lt;br /&gt;
For easy hosting for free, we require projects which need hosting to be dockerised (meaning they have a &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
These Docker containers cannot interact with the host system at all.&lt;br /&gt;
&lt;br /&gt;
== Websites ==&lt;br /&gt;
&lt;br /&gt;
If all pages can be written in markdown, then use a static site generator.&lt;br /&gt;
&lt;br /&gt;
If a website is dynamic it should always be designed with a backend and frontend to help with code security, which also helps with a website feeling “modern”.&lt;br /&gt;
&lt;br /&gt;
Any data input should be validated on the backend.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Design&amp;diff=113</id>
		<title>BOSS/Design</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Design&amp;diff=113"/>
		<updated>2026-06-04T07:48:45Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com written by hw2210 and pcs47&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;We don’t want to specify any harsh requirements that would prevent students creating a variety of projects.&lt;br /&gt;
&lt;br /&gt;
However, we do need to give some recommendations for designing projects, so that future students can still understand and maintain the projects.&lt;br /&gt;
&lt;br /&gt;
It is also worth noting that due to the requirement of hosting things for free, you will want to read the [[BOSS/Projects/Hosting|hosting page]] as this limits what you can do signficantly.&lt;br /&gt;
&lt;br /&gt;
Please make sure to follow the [[BOSS/Languages|coding guidelines]] as well for any programming.&lt;br /&gt;
&lt;br /&gt;
== KISS ==&lt;br /&gt;
&lt;br /&gt;
Keep it simple stupid!&lt;br /&gt;
&lt;br /&gt;
If it doesn’t require a feature, don’t add it.&lt;br /&gt;
&lt;br /&gt;
If it doesn’t require being written in a fancy programming language, don’t use that.&lt;br /&gt;
&lt;br /&gt;
If it doesn’t require data collection, don’t collect people’s data!&lt;br /&gt;
&lt;br /&gt;
== Existing Systems ==&lt;br /&gt;
&lt;br /&gt;
If a system exists, please try to use it unless its completely impractical.&lt;br /&gt;
&lt;br /&gt;
This is because students don’t have much time and don’t want to be managing a completely new system. However please use open source projects (closed source nearly always comes with a cost, either data collection or money both of which we cannot afford)&lt;br /&gt;
&lt;br /&gt;
Note: I’m mostly referring to libraries. I think its quite fun to write simple projects like a link shortener for yourself.&lt;br /&gt;
&lt;br /&gt;
=== Licensing ===&lt;br /&gt;
&lt;br /&gt;
Please see [https://boss.bathcs.com/policies/licenses/|our license policy] for information on how our projects are licensed.&lt;br /&gt;
&lt;br /&gt;
In general, new projects should be licensed under [https://spdx.org/licenses/LLVM-exception.html &amp;lt;code&amp;gt;Apache-2.0 WITH LLVM-Exception&amp;lt;/code&amp;gt;], contributors must agree to the [https://developercertificate.org/ Developers Certificate of Origin (DCO)], and any contributions must not violate the [https://www.bath.ac.uk/publications/university-of-bath-ip-policy/ University of Bath’s Intellectual Property policy].&lt;br /&gt;
&lt;br /&gt;
== Keep it general ==&lt;br /&gt;
&lt;br /&gt;
Always try to keep the structure of the project general, so that it can be repurposed easily. Don’t take it too far though—there’s a balance.&lt;br /&gt;
&lt;br /&gt;
Generalisation normally leads to cleaner code and better maintainability.&lt;br /&gt;
&lt;br /&gt;
== Modules ==&lt;br /&gt;
&lt;br /&gt;
Modules are your friend, different languages implement them differently and have different ideas about how they should be organised (e.g. Go vs Rust).&lt;br /&gt;
&lt;br /&gt;
We don’t really have opinions, as you should use the style which fits the programming language you are using. If you don’t like that style, don’t write a project in that language (for maintainers, I’m sorry but deal with it).&lt;br /&gt;
&lt;br /&gt;
If you try to go against the style of the programming language, you will find more barriers and have to implement hacky workarounds which will lead to unreadable, confusing and buggy code.&lt;br /&gt;
&lt;br /&gt;
== Data storage ==&lt;br /&gt;
&lt;br /&gt;
Due to limitations with the fact we will never get any funding, we must create projects which only relies on database storage (which we can host for free, see the [[BOSS/Projects/Database|database page]]).&lt;br /&gt;
&lt;br /&gt;
== Docker ==&lt;br /&gt;
&lt;br /&gt;
For easy hosting for free, we require projects which need hosting to be dockerised (meaning they have a &amp;lt;code&amp;gt;Dockerfile&amp;lt;/code&amp;gt;).&lt;br /&gt;
&lt;br /&gt;
These Docker containers cannot interact with the host system at all.&lt;br /&gt;
&lt;br /&gt;
== Websites ==&lt;br /&gt;
&lt;br /&gt;
If all pages can be written in markdown, then use a static site generator.&lt;br /&gt;
&lt;br /&gt;
If a website is dynamic it should always be designed with a backend and frontend to help with code security, which also helps with a website feeling “modern”.&lt;br /&gt;
&lt;br /&gt;
Any data input should be validated on the backend.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=BOSS/Projects&amp;diff=112</id>
		<title>BOSS/Projects</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=BOSS/Projects&amp;diff=112"/>
		<updated>2026-06-04T07:45:36Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[BOSS]] want to encourage all students to contribute to our organisation [https://github.bath.ac.uk/cs cs], either by improving our current systems or adding a project to the organisation, which then we can potentially host on the bathcs.com domain.&lt;br /&gt;
&lt;br /&gt;
== Mirroring ==&lt;br /&gt;
&lt;br /&gt;
Due to some projects’ complexity and the fact that original author may want to use their code to gain respect on GitHub or use it for jobs, we allow people to just mirror their project into the organisation.&lt;br /&gt;
&lt;br /&gt;
However we do require the project to be stored in the organisation for maintainability and future reference.&lt;br /&gt;
&lt;br /&gt;
If you are mirroring, please make sure that the current version on the GitHub is the version which is hosted on bathcs.com or is currently used. The project description on Bath GitLab must link to the canonical (original) source code.&lt;br /&gt;
&lt;br /&gt;
=== Licensing ===&lt;br /&gt;
&lt;br /&gt;
Please see [https://boss.bathcs.com/policies/licenses/|our license policy] for information on how our projects are licensed.&lt;br /&gt;
&lt;br /&gt;
In general, new projects should be licensed under [https://spdx.org/licenses/LLVM-exception.html &amp;lt;code&amp;gt;Apache-2.0 WITH LLVM-Exception&amp;lt;/code&amp;gt;], contributors must agree to the [https://developercertificate.org/ Developers Certificate of Origin (DCO)], and any contributions must not violate the [https://www.bath.ac.uk/publications/university-of-bath-ip-policy/ University of Bath’s Intellectual Property policy].&lt;br /&gt;
&lt;br /&gt;
== Requirements ==&lt;br /&gt;
&lt;br /&gt;
Due to most projects being tested by chaotic Computer Science students as well as hosted publicly, we have a high set of requirements for projects and things which need to be considered.&lt;br /&gt;
&lt;br /&gt;
More requirements are hidden within the pages in this wiki, so please make sure to read any relevant pages. Hopefully we have made this at least somewhat entertaining to read or at least easy to skim.&lt;br /&gt;
&lt;br /&gt;
=== To consider ===&lt;br /&gt;
&lt;br /&gt;
* Hand over procedure: Is there anything which cannot be handed over by our current procedures, if so how?&lt;br /&gt;
&lt;br /&gt;
=== Functional ===&lt;br /&gt;
&lt;br /&gt;
* Include credits for who wrote it and contact details once you leave the University.&lt;br /&gt;
* All systems must comply with our data collection + security policies (to be written)&lt;br /&gt;
* If an admin account is required for some parts, please use auth.bathcs.com to validate the user - documentation to be written :P&lt;br /&gt;
* GitHub main branch should be protected (except on mirrors?)&lt;br /&gt;
* Follow the code guidelines in [[./languages/index.md|our code guidelines page]]&lt;br /&gt;
* Follow the design principles in [[./design.md|our design page]]&lt;br /&gt;
* Get someone to review your code (this helps with not having any security issues)&lt;br /&gt;
* All data input should be validated on the backend (not just the frontend)&lt;br /&gt;
* Documentation should be included to setup a development environment as well as production&lt;br /&gt;
&lt;br /&gt;
=== Non-functional ===&lt;br /&gt;
&lt;br /&gt;
* Hosting needs to be free (we will not get any funding)&lt;br /&gt;
* Webpages should be light and fast to load (so we support all type of devices)&lt;br /&gt;
* A webpage should work on mobile (because students are lazy).&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Advertising_events&amp;diff=111</id>
		<title>Advertising events</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Advertising_events&amp;diff=111"/>
		<updated>2026-06-04T07:41:18Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com (written by Penn Mckintosh - pm2022)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Note|text=&lt;br /&gt;
The information in this page may only be applicable to SU-affiliated departmental societies. Other events can be advertised through subsets of the methods on this page.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Emails ==&lt;br /&gt;
&lt;br /&gt;
We need to send emails via both the department and the SU, such that everyone gets them. A lot of computer science students show up to our events despite not being members, so it’s important to include everyone. However, we don’t want to send the same email to anyone twice. We send emails to all computer science students via the Directors of Studies/Teaching, and then to all remaining members via the SU website.&lt;br /&gt;
&lt;br /&gt;
== Using the templating system ==&lt;br /&gt;
&lt;br /&gt;
# Download the templating tool&lt;br /&gt;
## Install [https://www.php.net/manual/en/install.php PHP 8 (command line setup)] and [https://wiki.python.org/moin/BeginnersGuide/Download Python 3]&lt;br /&gt;
## Download the latest .zip file from [https://gitlab.bath.ac.uk/cs/bcss/su-mail/-/releases su-mail releases]&lt;br /&gt;
## Extract the downloaded file into a folder&lt;br /&gt;
# Create an email&lt;br /&gt;
## Copy one of the existing emails from the emails folder and increment the number in the filename&lt;br /&gt;
## Edit the content of the email&lt;br /&gt;
## Make sure to put &amp;lt;code&amp;gt;rel=&amp;amp;quot;noopener noreferer&amp;amp;quot;&amp;lt;/code&amp;gt; when using &amp;lt;code&amp;gt;&amp;amp;lt;a&amp;amp;gt;&amp;lt;/code&amp;gt; tags&lt;br /&gt;
# Generate the email&lt;br /&gt;
## Run &amp;lt;code&amp;gt;python main.py emails/xyz.json&amp;lt;/code&amp;gt;&lt;br /&gt;
## The email’s HTML should be in your clipboard as well as output in the terminal&lt;br /&gt;
&lt;br /&gt;
=== Via the SU ===&lt;br /&gt;
&lt;br /&gt;
The [https://www.thesubath.com/resources/thesu/SU-Committee-member-web-tools-guide/ committee web tools] document has a detailed explanation of how to send an email via the SU website. It also has some basic debugging steps.&lt;br /&gt;
&lt;br /&gt;
For BCSS, the process is slightly more complicated. We don’t want to send emails to any Computer Science students via the SU, as they will receive the email from the Directors of Studies/Teaching. The SU doesn’t have any way to filter these, so it has to be done manually, following these steps:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Download the exclusion tool&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Download the latest .jar file from [https://gitlab.bath.ac.uk/cs/bcss/su-members/-/releases the su-exclude releases page found here]&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Ensure you have Java Runtime 1.8 or higher installed&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Download the sales report&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Open [https://www.thesubath.com/organisation/salesreports/6566/ the SU’s sales report page].&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Set the &#039;&#039;&#039;Start date&#039;&#039;&#039; to 01/08/YYYY (where YYYY is set so that 01/08/YYYY is within the last year). Make sure that the &#039;&#039;&#039;End date&#039;&#039;&#039; is in the future (this should be the default).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Click &#039;&#039;&#039;Purchasers report&#039;&#039;&#039;.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Scroll down and click the &#039;&#039;&#039;Save&#039;&#039;&#039; icon:&amp;lt;/p&amp;gt;&lt;br /&gt;
[[File:events-save.png|Save button to the left of a printer icon]]&lt;br /&gt;
&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Select &#039;&#039;&#039;XML file with report data&#039;&#039;&#039;:&amp;lt;/p&amp;gt;&lt;br /&gt;
[[File:events-xml.png|Text in the center showing XML file with report data]]&lt;br /&gt;
&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Download and &#039;&#039;&#039;save&#039;&#039;&#039; the XML file in your Downloads folder.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Connect to the Intranet&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Either go to campus and connect to eduroam, or&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;[https://connect.bath.ac.uk/ Connect to the University VPN]&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Run the tool&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Put the downloaded JAR and downloaded XML in the same folder/directory&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a command prompt/terminal in that folder/directory&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Run &amp;lt;code&amp;gt;java -jar suexclude-&amp;amp;lt;VERSION&amp;amp;gt;.jar &amp;amp;quot;Product Purchasers Report (System).xml&amp;amp;quot; --deduplicate --include-product 10000610 --exclude-ou DEP-COMP&amp;lt;/code&amp;gt;, adjusting filenames as necessary. This step can take a long time to complete, please be patient! If it hangs, you probably forgot to connect the VPN/connect eduroam on campus. If the VPN is connected or you are on the campus Intranet, then please just wait, it will load eventually!&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Copy the output to the clipboard (it should be valid JavaScript, otherwise there was an error).&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Delete the report file&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Make sure to delete the Product Purchasers Report from your computer.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Clear the recycle bin!&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Set recipients&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open [https://www.thesubath.com/organisation/messages/6566/ the SU messages page].&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Create or open a draft.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Click &#039;&#039;&#039;Add recipients&#039;&#039;&#039;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open DevTools using &#039;&#039;&#039;Ctrl+Shift+C&#039;&#039;&#039;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;If necessary, press &#039;&#039;&#039;Escape&#039;&#039;&#039; to show the JavaScript console.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Paste the output of the tool into the JavaScript console prompt.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Close DevTools.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Click &#039;&#039;&#039;Add recipients&#039;&#039;&#039;.&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Clear your clipboard&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Copy some non-confidential text.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;If you use a clipboard manager, make sure to clear that too.&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Write your email&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Paste the HTML from the [[#using-the-templating-system|templator]] into the Source input.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Click &#039;&#039;&#039;Save as draft&#039;&#039;&#039;.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Once ready, let another committee member &#039;&#039;&#039;double-check&#039;&#039;&#039; your email.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Send the same email via the Director of Studies/Teaching and wait for them to forward it.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Once the Director of Studies/Teaching have sent it, disable Google Campaign Tracking then click &#039;&#039;&#039;Send&#039;&#039;&#039; on the SU page.&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Debugging:&lt;br /&gt;
&lt;br /&gt;
# If Java throws an error not related to the tool, debug it using your favourite search engine.&lt;br /&gt;
# If the only output is &amp;lt;code&amp;gt;var r=null;&amp;lt;/code&amp;gt; then you downloaded the wrong report, or set the wrong date range.&lt;br /&gt;
&lt;br /&gt;
=== Via the Director of Studies/Teaching ===&lt;br /&gt;
&lt;br /&gt;
Send an email to Zack Lyons or John Benardis, both are happy to send (relevant) emails to the entire department. Zack is often really busy so John has been more responsive lately. It is worth noting Fabio sends emails targetted at PG-T students.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Ensure the email HTML is in your clipboard by running the [[#using-the-templating-system|templator]].&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Open [https://outlook.office.com/mail/ Outlook on the Web].&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Compose a new email to Zack Lyons. ([mailto:zl221@bath.ac.uk zl221@bath.ac.uk]) or John Benardis ([mailto:ib322@bath.ac.uk ib322@bath.ac.uk]).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Type an introduction just saying hi and asking them to forward the below email to students (specify whether it is for UG, PG-T or PG-R).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Where you want the email to go, type the word “test”.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Hold shift and right-click the word “test”.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Click “inspect”. You should see something like this:&amp;lt;/p&amp;gt;&lt;br /&gt;
[[File:events-html.png|HTML code, highlighting “div” with class “elementToProof”]]&lt;br /&gt;
&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Right-click anywhere in the blue highlighted area (text “elementToProof”) and select “Edit as HTML”.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Select the entire textbox and paste (replacing the existing content).&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Send the email.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;After the email has been forwarded to relevant students, make sure to click send on the SU page!&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Discord ==&lt;br /&gt;
&lt;br /&gt;
== Instagram ==&lt;br /&gt;
&lt;br /&gt;
{{Note|text=This section is a work in progress.}}&lt;br /&gt;
&lt;br /&gt;
* Make sure that any Instagram posts have alt-text and a useful caption. Make sure that all information in the photo is duplicated in the caption.&lt;br /&gt;
&lt;br /&gt;
== WhatsApp ==&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=File:Events-html.png&amp;diff=110</id>
		<title>File:Events-html.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=File:Events-html.png&amp;diff=110"/>
		<updated>2026-06-04T07:38:53Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: HTML code, highlighting “div” with class “elementToProof”&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
HTML code, highlighting “div” with class “elementToProof”&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=File:Events-xml.png&amp;diff=109</id>
		<title>File:Events-xml.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=File:Events-xml.png&amp;diff=109"/>
		<updated>2026-06-04T07:38:37Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Text in the center showing XML file with report data&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
Text in the center showing XML file with report data&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=File:Events-save.png&amp;diff=108</id>
		<title>File:Events-save.png</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=File:Events-save.png&amp;diff=108"/>
		<updated>2026-06-04T07:38:20Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Save button to the left of a printer icon&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Summary ==&lt;br /&gt;
Save button to the left of a printer icon&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=StrongSwan_VPN_on_Linux&amp;diff=107</id>
		<title>StrongSwan VPN on Linux</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=StrongSwan_VPN_on_Linux&amp;diff=107"/>
		<updated>2026-06-04T07:33:23Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com (written by Peter Chaplin-Smith - pcs47)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The GlobalProtect VPN has been decommissioned by DDaT. Unfortunately, this was the only easily working VPN on Linux. The new strongSwan VPN &#039;&#039;should&#039;&#039; just work on popular distros like Mint with the instructions given at https://connect.bath.ac.uk/. Unfortunately, for the rest of us, it often does not “just work”.&lt;br /&gt;
&lt;br /&gt;
This page contains troubleshooting steps for if a basic connection doesn’t work.&lt;br /&gt;
&lt;br /&gt;
= Connect instructions =&lt;br /&gt;
&lt;br /&gt;
This section covers the basic connection instructions from https://connect.bath.ac.uk and what to expect for a successful connection. This might help your troubleshooting. If it doesn’t, giving DDaT information about what happens here might be useful.&lt;br /&gt;
&lt;br /&gt;
# Install the correct packages. The name of these packages differs from distro to distro, so you might have to do some searching. These packages (in the Ubuntu &amp;lt;code&amp;gt;apt&amp;lt;/code&amp;gt; repositories) are &amp;lt;code&amp;gt;libcharon-extra-plugins&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;network-manager-strongswan&amp;lt;/code&amp;gt;, and possibly &amp;lt;code&amp;gt;network-manager-strongswan-gnome&amp;lt;/code&amp;gt; (if you’re using the Gnome desktop environment).&lt;br /&gt;
# Using the NetworkManager GUI (might just be called network settings or similar), add a VPN connection with the following:&lt;br /&gt;
#* Connection Name: University of Bath VPN&lt;br /&gt;
#* Server Name: vpn.bath.ac.uk&lt;br /&gt;
#* VPN Type: IKEv2&lt;br /&gt;
#* Type of sign-in: Username and Password/EAP-MSCHAPv2 or simply “EAP”&lt;br /&gt;
#* Other Options: Request an inner IP address, Enforce UDP encapsulation&lt;br /&gt;
# You may need to use the university’s DNS servers (&amp;lt;code&amp;gt;138.38.1.1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;138.38.1.2&amp;lt;/code&amp;gt;) or a public DNS server like Cloudflare’s &amp;lt;code&amp;gt;1.1.1.1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
The instructions also give the following troubleshooting advice:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;blockquote&amp;gt;You may also need to run the command below if you find DNS lookups fail for some university resources:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;sudo ln -sfv /run/systemd/resolve/resolv.conf /etc/resolv.conf&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
Though, we’re not sure this is a great idea, nor are we sure this will work on any distro.&lt;br /&gt;
&lt;br /&gt;
== What to expect ==&lt;br /&gt;
&lt;br /&gt;
Now that you’ve followed the university’s instructions, you should be able to connect using the NetworkManager GUI, it should ask you for a username and password (if you haven’t already supplied the username in the configuration), put your university credentials in (the username should be &#039;&#039;&#039;without&#039;&#039;&#039; the &amp;lt;code&amp;gt;@bath.ac.uk&amp;lt;/code&amp;gt;), and you should get a Microsoft authenticator request via the app.&lt;br /&gt;
&lt;br /&gt;
To confirm you are connected to the university VPN correctly, check you can visit any internal site. For example, go to https://2024-25.moodle-archive.bath.ac.uk/. If the page loads, you’re connected! If not, you’ll need to do some troubleshooting.&lt;br /&gt;
&lt;br /&gt;
= Troubleshooting =&lt;br /&gt;
&lt;br /&gt;
First, wipe the slate clean (delete the connection), update your system, and reboot. Once you’ve done that, try again. This should narrow the chances of your system being in some weird state. If it works, great! If not, it’s time to get troubleshooting.&lt;br /&gt;
&lt;br /&gt;
Some of the following sections will require the use of the command line. If you’re unfamiliar, there are plenty of good guides out there and unfortunately teaching the command line is out of scope for this document. We assume basic command line competence from here on out.&lt;br /&gt;
&lt;br /&gt;
== I can’t add the connection whatsoever ==&lt;br /&gt;
&lt;br /&gt;
First of all, check if &amp;lt;code&amp;gt;NetworkManager&amp;lt;/code&amp;gt; has the connection saved, you can list connections with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;nmcli connection&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Or, &amp;lt;code&amp;gt;nmcli&amp;lt;/code&amp;gt; has short-hand commands:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;nmcli c&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This should list all your connections, look for one with the name you gave it (which would be &amp;lt;code&amp;gt;University of Bath VPN&amp;lt;/code&amp;gt; if you followed the instructions in [[#connect-instructions|the first section]]). If it does exist, try connecting with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;nmcli c up &amp;quot;University of Bath VPN&amp;quot; --ask&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You’ll be asked for your password and you should get a Microsoft authenticator request. If you get a successful connection as per [[#what-to-expect|this section]], great! If not and you didn’t get a Microsoft authenticator request, carry on reading this section. If you didn’t get a successful connection but did get a Microsoft authenticator request, check the next section.&lt;br /&gt;
&lt;br /&gt;
First, let’s clean the slate. Check your connections with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;nmcli c&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Then delete the VPN connections by their UUID (UUIDs look something like &amp;lt;code&amp;gt;7abdc95a-4b2a-42bb-9a4a-38104d288737&amp;lt;/code&amp;gt;, yours will be different):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;nmcli c delete &amp;lt;uuid of the VPN connection&amp;gt;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, we’d suggest adding the connection via &amp;lt;code&amp;gt;nmcli&amp;lt;/code&amp;gt;, the command line for &amp;lt;code&amp;gt;NetworkManager&amp;lt;/code&amp;gt;. To do this, you can use the following command. &#039;&#039;&#039;Note that you’ll have replace the placeholder text with your Bath username&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;nmcli connection add \&lt;br /&gt;
    con-name &amp;quot;University of Bath VPN&amp;quot; \&lt;br /&gt;
    type vpn \&lt;br /&gt;
    vpn-type org.freedesktop.NetworkManager.strongswan \&lt;br /&gt;
    ifname &amp;quot;*&amp;quot; \&lt;br /&gt;
    ipv4.dns &amp;quot;138.38.1.1,138.38.1.2&amp;quot; \&lt;br /&gt;
    ipv4.ignore-auto-dns yes \&lt;br /&gt;
    vpn.data &amp;quot;address=vpn.bath.ac.uk, method=eap, user=&amp;lt;your bath username without the @bath.ac.uk&amp;gt;, encap=yes, virtual=yes&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
This sets up a strongSwan VPN connection using the university’s DNS servers and the &amp;lt;code&amp;gt;vpn.bath.ac.uk&amp;lt;/code&amp;gt; VPN server. It should also use your username, set UDP encapsulation enforcement, and should request an inner IP.&lt;br /&gt;
&lt;br /&gt;
You can now attempt to connect again, following the same steps as before. If you get a connection, great! If not, look for the section that best describes your issue.&lt;br /&gt;
&lt;br /&gt;
== I’m not getting a Microsoft authenticator request ==&lt;br /&gt;
&lt;br /&gt;
Before you start this section, make sure to follow the instructions in the previous subsection so you’re in a known state.&lt;br /&gt;
&lt;br /&gt;
When you connect with &amp;lt;code&amp;gt;nmcli c up &amp;amp;quot;University of Bath VPN --ask&amp;amp;quot;&amp;lt;/code&amp;gt;, if you get an error such as:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;Error: Connection activation failed: No valid secrets&amp;lt;/pre&amp;gt;&lt;br /&gt;
You might need a certificate, or you may have entered your password incorrectly. To check the latter, delete the VPN connection and try again.&lt;br /&gt;
&lt;br /&gt;
If you’re not getting a Microsoft authenticator request still, you might need a certificate. The university uses Sectigo certificates for the strongSwan VPN. These &#039;&#039;should&#039;&#039; be bundled in your distro’s trust store, but either &amp;lt;code&amp;gt;charon-nm&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;NetworkManager&amp;lt;/code&amp;gt; might not be checking for them properly and so your connection might be failing. Unfortunately, there is no easy way with a VPN connection to get around this.&lt;br /&gt;
&lt;br /&gt;
The following instructions are a bit messy, but are mostly distro-agnostic and &#039;&#039;should&#039;&#039; resolve the issue &#039;&#039;if it is a certificate problem&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
There are some extra steps you can do first to confirm it is a certificate issue, though different distros package software differently and so this isn’t guaranteed to give results:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;# look for anything that suggests a certificate is missing or untrusted&lt;br /&gt;
journalctl -u NetworkManager | grep charon&lt;br /&gt;
&lt;br /&gt;
# if the above doesn&#039;t show anything, try just looking through NetworkManager&#039;s logs&lt;br /&gt;
journalctl -u NetworkManager&lt;br /&gt;
&lt;br /&gt;
# if that doesn&#039;t immediately show anything, you can try the following&lt;br /&gt;
journalctl | grep Sectigo&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Anything that &#039;&#039;does&#039;&#039; come up may be useful to DDaT for troubleshooting if our troubleshooting steps don’t resolve the issue.&lt;br /&gt;
&lt;br /&gt;
Firstly, you’ll have to download the &amp;lt;code&amp;gt;Sectigo Public Server Authentication Root R46&amp;lt;/code&amp;gt; certificate. This can be found here: https://crt.sh/?d=4256644734. If you don’t trust us or the certificate has changed, find the correct certificate on this page: https://www.sectigo.com/knowledge-base/detail/Sectigo-Public-Intermediates-and-Roots.&lt;br /&gt;
&lt;br /&gt;
Once you’ve got the certificate, move it somewhere you won’t accidentally delete it. We recommend something like &amp;lt;code&amp;gt;~/.uni-vpn/sectigo-root.crt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Now, delete your VPN connection (again, just to make sure you’re in a known state) and add the following connection. &#039;&#039;&#039;You will need to replace your username AND the path to the Sectigo root certificate&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;nmcli connection add \&lt;br /&gt;
    con-name &amp;quot;University of Bath VPN&amp;quot; \&lt;br /&gt;
    type vpn \&lt;br /&gt;
    vpn-type org.freedesktop.NetworkManager.strongswan \&lt;br /&gt;
    ifname &amp;quot;*&amp;quot; \&lt;br /&gt;
    ipv4.dns &amp;quot;138.38.1.1,138.38.1.2&amp;quot; \&lt;br /&gt;
    ipv4.ignore-auto-dns yes \&lt;br /&gt;
    vpn.data &amp;quot;address=vpn.bath.ac.uk, method=eap, user=&amp;lt;your bath username without the @bath.ac.uk&amp;gt;, encap=yes, virtual=yes, certificate=/full/path/to/sectigo-root.crt&amp;quot;&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If you used our suggested location, you can replace the certificate path with &amp;lt;code&amp;gt;$HOME/.uni-vpn/sectigo-root.crt&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Attempt to connect as before. If you get a Microsoft authenticator request but don’t get a connection afterwards, go to the next section. If you don’t get a Microsoft authenticator request, [[#file-a-ticket|file a ticket]].&lt;br /&gt;
&lt;br /&gt;
== I get a Microsoft authenticator request, but no connection ==&lt;br /&gt;
&lt;br /&gt;
Firstly, try to connect again, accept the Microsoft authenticator request, and then wait ~40-50 seconds without retrying. If you get a secondary Microsoft authenticator request, this may be due to a routing misconfiguration.&lt;br /&gt;
&lt;br /&gt;
First, check for a rule under &amp;lt;code&amp;gt;210&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;220&amp;lt;/code&amp;gt; with the following command:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;ip rule&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
If you don’t see a line similar to:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;210:    not from all fwmark 0xd2 lookup 210&amp;lt;/pre&amp;gt;&lt;br /&gt;
You may have a configuration or routing issue. Double check you’ve installed all the correct packages for your distro.&lt;br /&gt;
&lt;br /&gt;
To test if routing is your issue, you can add the following route:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;ip route add 138.38.3.176/28 via &amp;lt;default gateway&amp;gt; table 210&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Replacing the default gateway with the IP after “default via” from running &amp;lt;code&amp;gt;ip r&amp;lt;/code&amp;gt;. Then, try connecting again. If that doesn’t work, [[#file-a-ticket|file a ticket]]. If that works, &#039;&#039;&#039;disconnect and delete the route&#039;&#039;&#039; by replacing the &amp;lt;code&amp;gt;add&amp;lt;/code&amp;gt; with &amp;lt;code&amp;gt;del&amp;lt;/code&amp;gt; in the above command. You can configure &amp;lt;code&amp;gt;NetworkManager&amp;lt;/code&amp;gt; to create the route for you when you connect to the VPN, this way it won’t be forever lingering on your system.&lt;br /&gt;
&lt;br /&gt;
To do so, you can run the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;nmcli c modify &amp;quot;University of Bath VPN&amp;quot; ipv4.route-table 210&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
You may need to restart the &amp;lt;code&amp;gt;NetworkManager&amp;lt;/code&amp;gt; daemon, &#039;&#039;&#039;this will temporarily disconnect you&#039;&#039;&#039;. On a &amp;lt;code&amp;gt;systemd&amp;lt;/code&amp;gt; based system, this can be done with:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;sh&amp;quot;&amp;gt;systemctl restart NetworkManager.service&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Now, you should be able to connect to the VPN without issues. If not, [[#file-a-ticket|file a ticket]].&lt;br /&gt;
&lt;br /&gt;
= File a ticket =&lt;br /&gt;
&lt;br /&gt;
If the troubleshooting steps given here don’t work, you should file a ticket with DDaT at: https://topdesk.bath.ac.uk/ (&amp;lt;code&amp;gt;DDaT Support: IT &amp;amp;amp; Audio Visual &amp;amp;gt; Report an Issue &amp;amp;gt; Report a Network Issue&amp;lt;/code&amp;gt;).&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=VaultTub&amp;diff=106</id>
		<title>VaultTub</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=VaultTub&amp;diff=106"/>
		<updated>2026-06-04T07:30:26Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Update to be more accurate to the current system&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;VaultTub is our self-hosted vaultwarden/bitwarden instance managed by [[Bath Open Source Society|BOSS]] which can be accessed via [https://vault.bathcs.com vault.bathcs.com]. This allows societies to store and share passwords freely amongst themselves.&lt;br /&gt;
&lt;br /&gt;
During handover, you may be messaged to create an account and join the societies organisation on thep platform.&lt;br /&gt;
&lt;br /&gt;
For docs of how to use this, see [https://bitwarden.com/help/ bitwarden’s documentation].&lt;br /&gt;
&lt;br /&gt;
== Quick setup ==&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Throughout this page we refer to “Owners of organisations”, from this we mean the delegated people who are the Owners of the Bitwarden organisation. Please see our [[#Quick note on owners|quick note on owners]] for who this should be.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
You should be message or told by the previous committee that you need to signup so they can invite you. All bath students can freely make an account (though we ask you not to store your personal passwords on there).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Go to [https://vault.bathcs.com vault.bathcs.com]&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Enter your uni email and click &amp;quot;Use single sign-on&amp;quot;&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Once redirected to our authentication site, enter your university username and password.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;You will be redirected back and asked to create a master password&amp;lt;/p&amp;gt;{{Note|text=If you lose the password you will not be able to recover it|type=warn}}&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;You may also receive emails during this process, if the email is in &#039;&#039;&#039;junk&#039;&#039;&#039;, there should be a dropdown at the top of the email with the option to make “vault@bathcs.com” never go to your junk folder. Please select this as you will get a lot more emails after this point.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&#039;&#039;&#039;REQUIRED&#039;&#039;&#039;: [[#Two-step login|Set up 2FA]]&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&#039;&#039;&#039;OPTIONAL&#039;&#039;&#039;: [[#Keys|Follow security recommendations]]&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&#039;&#039;&#039;REQUIRED&#039;&#039;&#039;: &#039;&#039;&#039;for [[#Quick note on owners|Owners of organisations]]&#039;&#039;&#039;: set up [[#Emergency Access|emergency access]]&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Previous committees/initiatives will add you to the relevant organisation as described in the handover procedure.&lt;br /&gt;
&lt;br /&gt;
{{Note|type=warn|text=&lt;br /&gt;
Please berate them with emails until they do this as (which has to happen before the summer holidays). This is because if they are uncontactable, all the passwords will be lost forever.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Recommended Settings ==&lt;br /&gt;
&lt;br /&gt;
Once you have signed up with a password you can remember (but don’t use anywhere else), we recommend updating the following settings:&lt;br /&gt;
&lt;br /&gt;
Settings can be found: “Profile Icon in top right &amp;amp;gt; account settings”.&lt;br /&gt;
&lt;br /&gt;
=== Two-step login ===&lt;br /&gt;
&lt;br /&gt;
{{Note|type=warn|text=&lt;br /&gt;
This is required for all committee members, please see our [[2FA|2fa page here]] for a more general overview of what this is.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Assuming you don’t have a security key, we recommend using the “Authenticator App” option (see [[2FA#Individuals|the instructions in 2fa section here]] for a more in-depth explanation of how to setup this).&lt;br /&gt;
&lt;br /&gt;
If you have a security key (e.g. Yubikey), use the FIDO2 WebAuthn option. All other options have not been enabled as that takes time and is not worth it.&lt;br /&gt;
&lt;br /&gt;
=== Keys ===&lt;br /&gt;
&lt;br /&gt;
I recommend upgrading to use Argon2id with settings:&lt;br /&gt;
&lt;br /&gt;
* KDF Iterations: 10&lt;br /&gt;
* KDF Memory: 64 (maximum that works with iOS)&lt;br /&gt;
* KDF Parrallelism 8&lt;br /&gt;
&lt;br /&gt;
=== Emergency Access ===&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
This is a &#039;&#039;&#039;requirement&#039;&#039;&#039; for all the [[#Quick note on owners|Owners of organisations]]&lt;br /&gt;
|type=warn}}&lt;br /&gt;
&lt;br /&gt;
We recommend setting up emergency access with at least 1 other person, this is for safety if you lose access to your account.&lt;br /&gt;
&lt;br /&gt;
Head to [https://vault.bathcs.com/#/settings/emergency-access the emergency access tab in the settings page] and click “Add emergency contact”, entering the users email (Note they need to have a VaultTub account).&lt;br /&gt;
&lt;br /&gt;
This should be required for users with a significant amount of power due to the risk of losing everything (we cannot recover your passwords).&lt;br /&gt;
&lt;br /&gt;
Note this does require about 3 emails of back and forth accepting with the person (I have been fooled into thinking it was over before when it was not).&lt;br /&gt;
&lt;br /&gt;
=== Using the extension/app ===&lt;br /&gt;
&lt;br /&gt;
Bitwarden has a [https://bitwarden.com/download/#downloads-web-browser browser extension] and an [https://bitwarden.com/download/#downloads-mobile app] which supports self-hosted instances (and multiple accounts).&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
If you wish, you can also set it up so you can approve sign-in requests when signing in somewhere else, which you may prefer over TOTP 2FA.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
To install the app or the extension:&lt;br /&gt;
&lt;br /&gt;
* Download the extension, from [https://bitwarden.com/download/#downloads-web-browser Bitwarden’s download page]&lt;br /&gt;
* Open it up&lt;br /&gt;
** if you already have an account you can click the profile icon and then click “Add account”.&lt;br /&gt;
* Under the input for the email address (set to “bitwarden” by default), you can select “self-hosted”.&lt;br /&gt;
* Input “https://vault.bathcs.com” for the server field and hit “Save” in the top right.&lt;br /&gt;
* Enter your login details for VaultTub.&lt;br /&gt;
&lt;br /&gt;
==== Recommended settings ====&lt;br /&gt;
&lt;br /&gt;
You may wish to change the default lockout period or add a pin, which can be done in the “Settings tab”. These are handled on a &#039;&#039;&#039;per account&#039;&#039;&#039; basis.&lt;br /&gt;
&lt;br /&gt;
You can click “unlock with pin” and enter a pin (unchecking “unlock with master on browser restart” if you don’t want that).&lt;br /&gt;
&lt;br /&gt;
You can also change the “Vault timeout”, however this is &#039;&#039;&#039;not&#039;&#039;&#039; recommended.&lt;br /&gt;
&lt;br /&gt;
==== Adding a Login ====&lt;br /&gt;
You can easily add a login by clicking the &amp;quot;New&amp;quot; button within the app, and filling out the details, along with the &amp;quot;website&amp;quot;. Please make sure the owner is set to the &#039;&#039;&#039;society this login should live under&#039;&#039;&#039;, as well as a collection set.&lt;br /&gt;
&lt;br /&gt;
From this interface you can also generate a password to fill in. The recommneded settings are:&lt;br /&gt;
&lt;br /&gt;
* &amp;amp;gt;= 25 characters&lt;br /&gt;
&lt;br /&gt;
&amp;lt;li&amp;gt;include special characters&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;min numbers: 2&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;min special characters: 2&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;uncheck “avoid ambiguous characters”&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Quick note on owners ==&lt;br /&gt;
&lt;br /&gt;
Make sure you have at least two owners to an organisation. This is so we have backups to make sure we don’t lose access to the data.&lt;br /&gt;
&lt;br /&gt;
For societies we recommend roles equivalent to chair and secretary. For other initiatives, we recommend you choose someone to act as the Owner.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
For security reasons, please do not grant access to external users unless necessary. Make sure to keep data secure, especially where sensitive or personal data is involved.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Owners also require [[#Emergency Access|emergency access]] to be set up with someone who is not another owner of their organisation.&lt;br /&gt;
&lt;br /&gt;
== Recommended Use ==&lt;br /&gt;
&lt;br /&gt;
This should be used for society passwords.&lt;br /&gt;
&lt;br /&gt;
These society passwords should be stored in relevant organisations. You have the power to create as many organisations you like and share them with other people.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
When you are no longer a member of a society your account will be disabled (but not deleted unless necessary).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Organisations ===&lt;br /&gt;
&lt;br /&gt;
To create an organisation, you can go to your “vaults” and click the “New organization” button on the side panel.&lt;br /&gt;
&lt;br /&gt;
Once created you can go to the “Organizations” tab in the top right and choose the organisation to manage, where you can invite new members (via “Members &amp;amp;gt; Invite Member”) or create a new collection (basically a folder which you can choose who has access to it).&lt;br /&gt;
&lt;br /&gt;
For each member you can choose the role and what collections they have permission to access, the rest is up to you on how you organise everything.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
When adding members, they will have to accept the invitation and then you will have to confirm them in the organisation interface.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Handover procedures ==&lt;br /&gt;
&lt;br /&gt;
Please see [https://boss.bathcs.com/handover/permissions/ our handover instructions].&lt;br /&gt;
&lt;br /&gt;
=== Rotating passwords ===&lt;br /&gt;
&lt;br /&gt;
As part of the handover procedures, it is recommended that each initiative rotates all the passwords stored in the organisation.&lt;br /&gt;
&lt;br /&gt;
To do this, please follow this rough instruction list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Open the item on VaultTub (either by the extension or website)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Copy the password and temporarily store it somewhere (e.g. in the notes section)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Visit the website&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Navigate to the change password section (it’s different for every website)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;On the extension, click “edit” on the item and click the “Generate Password” button and confirm that it will override the current password stored then&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Password recommendations:&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;amp;gt;= 25 characters&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;include special characters&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;min numbers: 2&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;min special characters: 2&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;uncheck “avoid ambiguous characters”&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Click “Save” on the item to save the new password&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Paste the new password in the change password fields + change the password&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Log out and log in again to make sure the new password has saved correctly&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Delete the temporary storage of the old password&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Add a line to the notes saying it was updated on the current date and include your name&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How to deal with the worst case scenarios ==&lt;br /&gt;
&lt;br /&gt;
If it is relating to the website being down, please contact [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk]. Note that I do not have any access to any of the data stored as it is all encrypted.&lt;br /&gt;
&lt;br /&gt;
=== I lost my password and don’t have Emergency Access ===&lt;br /&gt;
&lt;br /&gt;
In that case, there is nothing we can do. Your account must be deleted and you will need to be re-invited (this means you WILL lose access to any password you have on the account). Organisations passwords can be recovered by other members of the organisation.&lt;br /&gt;
&lt;br /&gt;
This is why you MUST either know your password by hand or store your password in your own password manager which also has a proper recovery procedure (which normally have to be emergency access contacts).&lt;br /&gt;
&lt;br /&gt;
=== The owner of the organisation is not responding ===&lt;br /&gt;
&lt;br /&gt;
If all [[#Quick note on owners|owners of the organisation]] are not responding or have lost access to their account, this is slightly more of an issue so make sure to have multiple owners.&lt;br /&gt;
&lt;br /&gt;
The organisation will have to be deleted and recreated. To save as many passwords as possible, get all other members to see what Collections they have access to and if they have permission to export the vault (found in the settings for the organisation).&lt;br /&gt;
&lt;br /&gt;
You want to then either export the vault data or copy every single password into a new organisation.&lt;br /&gt;
&lt;br /&gt;
== Create an organisation for your society ==&lt;br /&gt;
All societies are welcome to have organisations to share passwords as we believe in good password policies. However, only BOSS committee can create a new organisation for you, and so please submit a request by emailing [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk].&lt;br /&gt;
&lt;br /&gt;
They can do this through the [https://vault.bathcs.com/admin/users/overview admin interface] in the &amp;quot;Organisation&amp;quot; tab (however this interface is normally turned off and so has to be activated through a redeployment).&lt;br /&gt;
&lt;br /&gt;
== Instance Management stuff ==&lt;br /&gt;
&lt;br /&gt;
=== Backups ===&lt;br /&gt;
&lt;br /&gt;
Backups are handled by [[Bath Open Source Society|BOSS]] and is one of the few systems that get completely offsite backup on a third-party service along with the typical whole cluster backups.&lt;br /&gt;
&lt;br /&gt;
But the basic idea:&lt;br /&gt;
&lt;br /&gt;
* VaultTub get&#039;s backed up every 4 hours to our backup server.&lt;br /&gt;
* The whole cluster get&#039;s backed up every day to our backup server&lt;br /&gt;
* The specific VaultTub data (including passwords), get&#039;s backed up to [https://www.scaleway.com/en/ Scaleway] every day&lt;br /&gt;
&lt;br /&gt;
Contact [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk] if you have any questions. You can also see [https://gitlab.bath.ac.uk/cs/int/terraform/ our configuration within terraform].&lt;br /&gt;
&lt;br /&gt;
=== Emails ===&lt;br /&gt;
&lt;br /&gt;
Emails are sent through my SMTP server on the same network, which cannot receive emails (because of the firewall) but can send them.&lt;br /&gt;
&lt;br /&gt;
I should mention this has dkim, dmarc and spf setup to help with bypassing spam filters as well as being sent from a university IP (improving deliverability). From testing this works fine however may end up in junk folders from now and again.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=VaultTub&amp;diff=105</id>
		<title>VaultTub</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=VaultTub&amp;diff=105"/>
		<updated>2026-06-04T07:02:36Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;VaultTub is our self-hosted vaultwarden/bitwarden instance which can be accessed via [https://vault.bathcs.com vault.bathcs.com].&lt;br /&gt;
&lt;br /&gt;
During handover, you should be invited to sign up from vault@bathcs.com (it might be in the junk folder).&lt;br /&gt;
&lt;br /&gt;
For docs of how to use this, see [https://bitwarden.com/help/ bitwarden’s documentation].&lt;br /&gt;
&lt;br /&gt;
== Quick setup ==&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
Throughout this page we refer to “Owners of organisations”, from this we mean the delegated people who are the Owners of the Bitwarden organisation. Please see our [[#Quick note on owners|quick note on owners]] for who this should be.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
BathCS admins will invite you to VaultTub once you’ve been elected. Hopefully you will get an email from them directing you here.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ol style=&amp;quot;list-style-type: decimal;&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Once invited, go to the junk folder in Outlook and find the email&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;If the email is in &#039;&#039;&#039;junk&#039;&#039;&#039;, there should be a dropdown at the top of the email with the option to make “vault@bathcs.com” never go to your junk folder. Please select this as you will get a lot more emails after this point.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Follow the link in the email to sign up&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Enter your details and create an account:&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Use your University of Bath email (as it’s by invite only)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Make sure the password is strong (and &#039;&#039;&#039;isn’t&#039;&#039;&#039; used anywhere else) and you can remember it. Write it down somewhere safe if you are unsure.&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|type=warn|text=&lt;br /&gt;
&amp;lt;p&amp;gt;If you lose this password, you cannot recover it.&amp;lt;/p&amp;gt;&lt;br /&gt;
}}&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&#039;&#039;&#039;REQUIRED&#039;&#039;&#039;: [[#Two-step login|Set up 2FA]]&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&#039;&#039;&#039;OPTIONAL&#039;&#039;&#039;: [[#Keys|Follow security recommendations]]&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;&#039;&#039;&#039;REQUIRED&#039;&#039;&#039;: &#039;&#039;&#039;for [[#Quick note on owners|Owners of organisations]]&#039;&#039;&#039;: set up [[#Emergency Access|emergency access]]&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ol&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Previous committees/initiatives will add you to the relevant organisation as described in the handover procedure.&lt;br /&gt;
&lt;br /&gt;
{{Note|type=warn|text=&lt;br /&gt;
Please berate them with emails until they do this as (which has to happen before the summer holidays). This is because if they are uncontactable, all the passwords will be lost forever.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Recommended Settings ==&lt;br /&gt;
&lt;br /&gt;
Once you have signed up with a password you can remember (but don’t use anywhere else), we recommend updating the following settings:&lt;br /&gt;
&lt;br /&gt;
Settings can be found: “Profile Icon in top right &amp;amp;gt; account settings”.&lt;br /&gt;
&lt;br /&gt;
=== Two-step login ===&lt;br /&gt;
&lt;br /&gt;
{{Note|type=warn|text=&lt;br /&gt;
This is required for all committee members, please see our [[2FA|2fa page here]] for a more general overview of what this is.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Assuming you don’t have a security key, we recommend using the “Authenticator App” option (see [[2FA#Individuals|the instructions in 2fa section here]] for a more in-depth explanation of how to setup this).&lt;br /&gt;
&lt;br /&gt;
If you have a security key (e.g. Yubikey), use the FIDO2 WebAuthn option. All other options have not been enabled as that takes time and is not worth it.&lt;br /&gt;
&lt;br /&gt;
=== Keys ===&lt;br /&gt;
&lt;br /&gt;
I recommend upgrading to use Argon2id with settings:&lt;br /&gt;
&lt;br /&gt;
* KDF Iterations: 10&lt;br /&gt;
* KDF Memory: 64 (maximum that works with iOS)&lt;br /&gt;
* KDF Parrallelism 8&lt;br /&gt;
&lt;br /&gt;
=== Emergency Access ===&lt;br /&gt;
&lt;br /&gt;
{{Note|type|text=&lt;br /&gt;
This is a &#039;&#039;&#039;requirement&#039;&#039;&#039; for all the [[#Quick note on owners|Owners of organisations]]&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
We recommend setting up emergency access with at least 1 other person, this is for safety if you lose access to your account.&lt;br /&gt;
&lt;br /&gt;
Head to [https://vault.bathcs.com/#/settings/emergency-access the emergency access tab in the settings page] and click “Add emergency contact”, entering the users email (Note they need to have a VaultTub account).&lt;br /&gt;
&lt;br /&gt;
This should be required for users with a significant amount of power due to the risk of losing everything (we cannot recover your passwords).&lt;br /&gt;
&lt;br /&gt;
Note this does require about 3 emails of back and forth accepting with the person (I have been fooled into thinking it was over before when it was not).&lt;br /&gt;
&lt;br /&gt;
=== Using the extension/app ===&lt;br /&gt;
&lt;br /&gt;
Bitwarden has a [https://bitwarden.com/download/#downloads-web-browser browser extension] and an [https://bitwarden.com/download/#downloads-mobile app] which supports self-hosted instances (and multiple accounts).&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
If you wish, you can also set it up so you can approve sign-in requests when signing in somewhere else, which you may prefer over TOTP 2FA.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
To install the app or the extension:&lt;br /&gt;
&lt;br /&gt;
* Download the extension, from [https://bitwarden.com/download/#downloads-web-browser Bitwarden’s download page]&lt;br /&gt;
* Open it up&lt;br /&gt;
** if you already have an account you can click the profile icon and then click “Add account”.&lt;br /&gt;
* Under the input for the email address (set to “bitwarden” by default), you can select “self-hosted”.&lt;br /&gt;
* Input “https://vault.bathcs.com” for the server field and hit “Save” in the top right.&lt;br /&gt;
* Enter your login details for VaultTub.&lt;br /&gt;
&lt;br /&gt;
==== Recommended settings ====&lt;br /&gt;
&lt;br /&gt;
You may wish to change the default lockout period or add a pin, which can be done in the “Settings tab”. These are handled on a &#039;&#039;&#039;per account&#039;&#039;&#039; basis.&lt;br /&gt;
&lt;br /&gt;
You can click “unlock with pin” and enter a pin (unchecking “unlock with master on browser restart” if you don’t want that).&lt;br /&gt;
&lt;br /&gt;
You can also change the “Vault timeout”, however this is &#039;&#039;&#039;not&#039;&#039;&#039; recommended.&lt;br /&gt;
&lt;br /&gt;
== Quick note on owners ==&lt;br /&gt;
&lt;br /&gt;
Make sure you have at least two owners to an organisation. This is so we have backups to make sure we don’t lose access to the data.&lt;br /&gt;
&lt;br /&gt;
For societies we recommend roles equivalent to chair and secretary. For other initiatives, we recommend you choose someone to act as the Owner.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
For security reasons, please do not grant access to external users unless necessary. Make sure to keep data secure, especially where sensitive or personal data is involved.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
Owners also require [[#Emergency Access|emergency access]] to be set up with someone who is not another owner of their organisation.&lt;br /&gt;
&lt;br /&gt;
== Recommended Use ==&lt;br /&gt;
&lt;br /&gt;
This should be used for society passwords.&lt;br /&gt;
&lt;br /&gt;
These society passwords should be stored in relevant organisations. You have the power to create as many organisations you like and share them with other people.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
When you are no longer a member of a society your account will be disabled (but not deleted unless necessary).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
=== Organisations ===&lt;br /&gt;
&lt;br /&gt;
To create an organisation, you can go to your “vaults” and click the “New organization” button on the side panel.&lt;br /&gt;
&lt;br /&gt;
Once created you can go to the “Organizations” tab in the top right and choose the organisation to manage, where you can invite new members (via “Members &amp;amp;gt; Invite Member”) or create a new collection (basically a folder which you can choose who has access to it).&lt;br /&gt;
&lt;br /&gt;
For each member you can choose the role and what collections they have permission to access, the rest is up to you on how you organise everything.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
When adding members, they will have to accept the invitation and then you will have to confirm them in the organisation interface.&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Handover procedures ==&lt;br /&gt;
&lt;br /&gt;
Please see [../handover/permissions.md]&lt;br /&gt;
&lt;br /&gt;
=== Rotating passwords ===&lt;br /&gt;
&lt;br /&gt;
As part of the handover procedures, it is recommended that each initiative rotates all the passwords stored in the organisation.&lt;br /&gt;
&lt;br /&gt;
To do this, please follow this rough instruction list:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Open the item on VaultTub (either by the extension or website)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Copy the password and temporarily store it somewhere (e.g. in the notes section)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Visit the website&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Navigate to the change password section (it’s different for every website)&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;On the extension, click “edit” on the item and click the “Generate Password” button and confirm that it will override the current password stored then&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;Password recommendations:&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;amp;gt;= 25 characters&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;include special characters&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;min numbers: 2&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;min special characters: 2&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;uncheck “avoid ambiguous characters”&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Click “Save” on the item to save the new password&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Paste the new password in the change password fields + change the password&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Log out and log in again to make sure the new password has saved correctly&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Delete the temporary storage of the old password&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;Add a line to the notes saying it was updated on the current date and include your name&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== How to deal with the worst case scenarios ==&lt;br /&gt;
&lt;br /&gt;
If it is relating to the website being down, please contact [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk]. Note that I do not have any access to any of the data stored as it is all encrypted.&lt;br /&gt;
&lt;br /&gt;
=== I lost my password and don’t have Emergency Access ===&lt;br /&gt;
&lt;br /&gt;
In that case, there is nothing we can do. Your account must be deleted and you will need to be re-invited (this means you WILL lose access to any password you have on the account). Organisations passwords can be recovered by other members of the organisation.&lt;br /&gt;
&lt;br /&gt;
This is why you MUST either know your password by hand or store your password in your own password manager which also has a proper recovery procedure (which normally have to be emergency access contacts).&lt;br /&gt;
&lt;br /&gt;
=== The owner of the organisation is not responding ===&lt;br /&gt;
&lt;br /&gt;
If all [[#Quick note on owners|owners of the organisation]] are not responding or have lost access to their account, this is slightly more of an issue so make sure to have multiple owners.&lt;br /&gt;
&lt;br /&gt;
The organisation will have to be deleted and recreated. To save as many passwords as possible, get all other members to see what Collections they have access to and if they have permission to export the vault (found in the settings for the organisation).&lt;br /&gt;
&lt;br /&gt;
You want to then either export the vault data or copy every single password into a new organisation.&lt;br /&gt;
&lt;br /&gt;
== Instance Management stuff ==&lt;br /&gt;
&lt;br /&gt;
=== Inviting new users ===&lt;br /&gt;
&lt;br /&gt;
Only administrators (with the interface password) can invite new users.&lt;br /&gt;
&lt;br /&gt;
This can be done through the [https://vault.bathcs.com/admin/users/overview admin interface]. You simply type the users email in “Invite User” section.&lt;br /&gt;
&lt;br /&gt;
=== Backups ===&lt;br /&gt;
&lt;br /&gt;
This is handled by me (hw2210) as I am the maintainer of the NAS it is running on.&lt;br /&gt;
&lt;br /&gt;
But the basic idea:&lt;br /&gt;
&lt;br /&gt;
* Has an on-site iterative backup every day&lt;br /&gt;
* Has an off-site iterative backup every day&lt;br /&gt;
&lt;br /&gt;
Contact [mailto:su-boss@bath.ac.uk su-boss@bath.ac.uk] if you have any questions.&lt;br /&gt;
&lt;br /&gt;
=== Emails ===&lt;br /&gt;
&lt;br /&gt;
Emails are sent through my SMTP server on the same network, which cannot receive emails (because of the firewall) but can send them.&lt;br /&gt;
&lt;br /&gt;
I should mention this has dkim, dmarc and spf setup to help with bypassing spam filters. From testing this works fine however may end up in junk folders from now and again.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=Passwords&amp;diff=104</id>
		<title>Passwords</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=Passwords&amp;diff=104"/>
		<updated>2026-06-04T06:55:38Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Password security is important because, without [[2FA]], they are the only factor stopping an attacker from gaining access to your beloved accounts. This page serves as a basic guide to password security. For setting password policies in applications, follow [https://pages.nist.gov/800-63-4/ NIST SP800-63-4] and/or the [https://www.ncsc.gov.uk/collection/passwords NCSC’s advice].&lt;br /&gt;
&lt;br /&gt;
== TL;DR ==&lt;br /&gt;
&lt;br /&gt;
* Use passkeys instead of passwords where possible.&lt;br /&gt;
* If you can’t use a passkey, use a passphrase consisting of &#039;&#039;&#039;at least&#039;&#039;&#039; three words, separated by some symbol. Add numbers randomly. Capitalise if you want. Optionally, change this every so often so your passwords use different formats.&lt;br /&gt;
* Store your passphrase in a password manager. Use a longer passphrase and [[2FA]] to log into your password manager.&lt;br /&gt;
* Use [[2FA]] everywhere you possibly can.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;This can all be done with [[VaultTub]] for societies&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
# Log in to https://vault.bathcs.com/.&lt;br /&gt;
# On the sidebar, go to &amp;lt;code&amp;gt;Tools &amp;amp;gt; Generator&amp;lt;/code&amp;gt;.&lt;br /&gt;
# At the top, press &amp;lt;code&amp;gt;Passphrase&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Set:&lt;br /&gt;
#* The number of words to something greater than or equal to three.&lt;br /&gt;
#* The word separator to a random valid symbol of your choice.&lt;br /&gt;
#* Optionally, change the above two options every so often so your passwords use different formats.&lt;br /&gt;
# Copy the password and use it for whatever account.&lt;br /&gt;
# Save the login in VaultTub or your own password manager.&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
VaultTub is &#039;&#039;only&#039;&#039; for society usage. For personal passwords, we recommend [https://bitwarden.com/ Bitwarden] which is very similar (VaultTub uses a Bitwarden-compatible service called VaultWarden).&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
== Attacks ==&lt;br /&gt;
&lt;br /&gt;
When considering password security, it is important to consider what you are up against. Here is a non-exhaustive list of common attacks:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Phishing&#039;&#039;&#039;: tricking someone into revealing their password by some means (usually a fake website login from a dodgy email).&lt;br /&gt;
* &#039;&#039;&#039;Leaked Passwords&#039;&#039;&#039;: using passwords from data breaches. This is commonly associated with &#039;&#039;&#039;credential stuffing&#039;&#039;&#039;, where an attacker will attempt to reuse leaked credentials from one service on other services.&lt;br /&gt;
* &#039;&#039;&#039;Password Spraying&#039;&#039;&#039;: where an attacker tries many common passwords against a large number of accounts on a single service.&lt;br /&gt;
* &#039;&#039;&#039;Brute Force&#039;&#039;&#039;: the adversary tries a load of different passwords against a single account on a service. There are a few sub-types:&lt;br /&gt;
** &#039;&#039;&#039;Simple&#039;&#039;&#039;: try all possible passwords by incrementally cycling through all combinations.&lt;br /&gt;
** &#039;&#039;&#039;Dictionary&#039;&#039;&#039;: use a “wordlist” of common passwords found in breaches. The most well-known is [https://weakpass.com/wordlists/rockyou.txt rockyou], though it is pretty small with only a few million passwords. Some lists are in the billions.&lt;br /&gt;
** &#039;&#039;&#039;Hybrid&#039;&#039;&#039;: a &#039;&#039;sort of&#039;&#039; combination between dictionary and simple brute force attacks. Modifies words in a wordlist with common changes people make to passwords, for example if &amp;lt;code&amp;gt;password&amp;lt;/code&amp;gt; is in a wordlist, the attack might also check &amp;lt;code&amp;gt;Password123&amp;lt;/code&amp;gt;.&lt;br /&gt;
** &#039;&#039;&#039;Rainbow Table&#039;&#039;&#039;: an &#039;&#039;offline&#039;&#039; brute force attack, meaning the adversary needs the hash of your password, which they often don’t unless a breach has occurred. Essentially they build a lookup table of common passwords and their hashes for quick cracking of passwords.&lt;br /&gt;
* &#039;&#039;&#039;Shoulder Surfing&#039;&#039;&#039;: where an attacker either watches or records you typing in your password.&lt;br /&gt;
* &#039;&#039;&#039;Insecure Storage&#039;&#039;&#039;: passwords left in unsecured locations. For example, on a sticky note under a keyboard.&lt;br /&gt;
* &#039;&#039;&#039;Keyloggers&#039;&#039;&#039;: if a device has been infected with spyware, a keylogger can see what keys have been pressed and the attacker can see your password.&lt;br /&gt;
&lt;br /&gt;
== Mitigations ==&lt;br /&gt;
&lt;br /&gt;
Considering everything in [[#attacks|the attacks section]], what can we do to protect ourselves?&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Phishing&#039;&#039;&#039;:&lt;br /&gt;
** &#039;&#039;&#039;Use a password manager&#039;&#039;&#039;. A good password manager would not auto-fill on an incorrect domain name.&lt;br /&gt;
** &#039;&#039;Ideally&#039;&#039;, &#039;&#039;&#039;use a passkey&#039;&#039;&#039; as they &#039;&#039;&#039;will not&#039;&#039;&#039; work on an invalid domain.&lt;br /&gt;
* &#039;&#039;&#039;Leaked Passwords&#039;&#039;&#039;:&lt;br /&gt;
** &#039;&#039;&#039;Regularly check for your accounts in breaches&#039;&#039;&#039;. Some password managers have functionality built in to check all of your stored accounts against known breaches. Otherwise, there is [https://haveibeenpwned.com/ HIBP]. If an account has been found in a breach, rotate it. Otherwise, it is fine to leave it alone [https://pages.nist.gov/800-63-4/sp800-63b.html#passwordusability (see §8.1.2.1 of SP800-63B)].&lt;br /&gt;
** &#039;&#039;&#039;Use a password manager&#039;&#039;&#039;. With a password manager you don’t need to re-use the same password or variations across many accounts. You use &#039;&#039;&#039;one long password&#039;&#039;&#039; and [[2FA]] to access your password manager, then you can make all your other passwords completely unique, therefore mitigating credential stuffing.&lt;br /&gt;
** &#039;&#039;&#039;Use passkeys&#039;&#039;&#039;. A passkey will almost certainly be unique&amp;lt;ref&amp;gt;except in the &#039;&#039;&#039;extremely unlikely&#039;&#039;&#039; event a collision occurs.&amp;lt;/ref&amp;gt; to the breached site, therefore mitigating credential stuffing.&lt;br /&gt;
* &#039;&#039;&#039;Password Spraying&#039;&#039;&#039;:&lt;br /&gt;
** &#039;&#039;&#039;Use a password manager&#039;&#039;&#039;. With a password manager, you can make your passwords ridiculously long without ever having to memorise them all. Spraying attacks only really target common passwords.&lt;br /&gt;
** &#039;&#039;&#039;Use passkeys&#039;&#039;&#039;. There is no concept of “passkey spraying” as passkeys are all unique&amp;lt;ref&amp;gt;except in the &#039;&#039;&#039;extremely unlikely&#039;&#039;&#039; event a collision occurs.&amp;lt;/ref&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;Brute Force&#039;&#039;&#039;:&lt;br /&gt;
** &#039;&#039;&#039;Use a password manager&#039;&#039;&#039;. Password managers should have built in password/passphrase generators. The [https://www.ncsc.gov.uk/blog-post/three-random-words-or-thinkrandom-0 NCSC recommends passphrases with 3 or more words]. We would also recommend changing the settings of your passphrase generation every so often. If one of your passwords shows up in a breach and the attacker knows the wordlist you use for your passphrases and the format you use (i.e. word separators, capitalisation, number of words), this might speed up brute force efforts for other accounts.&lt;br /&gt;
** &#039;&#039;&#039;Use passkeys&#039;&#039;&#039;. Brute-forcing a passkey can only really be done with a simple brute force attack which should be impossible with current hardware and cryptography.&lt;br /&gt;
* &#039;&#039;&#039;Shoulder surfing&#039;&#039;&#039;:&lt;br /&gt;
** &#039;&#039;&#039;Use a password manager and 2FA&#039;&#039;&#039;. For logging into accounts, your password manager should be able to auto-fill, therefore an adversary can’t see you type your password. The one exception to this is the password to your password manager. If you enable [[2FA]], this is less of an issue because they’d then need to brute force anyways.&lt;br /&gt;
** &#039;&#039;&#039;Use passkeys&#039;&#039;&#039;. Can’t shoulder surf something you don’t type.&lt;br /&gt;
** &#039;&#039;&#039;Privacy screens&#039;&#039;&#039;. While not perfect, these can block shoulder surfers from seeing what you’re doing on your computer or phone, therefore making it a bit harder to grab your credentials if you type them in.&lt;br /&gt;
* &#039;&#039;&#039;Insecure Storage&#039;&#039;&#039;:&lt;br /&gt;
** &#039;&#039;&#039;Use a password manager&#039;&#039;&#039;. Password managers are meant to be secure storage for passwords.&lt;br /&gt;
** &#039;&#039;&#039;Use passkeys&#039;&#039;&#039;. Passkeys are pretty much always stored securely somewhere. Whether that is within a secure enclave on your device, on your password manager’s infrastructure, or on a physical security key.&lt;br /&gt;
* &#039;&#039;&#039;Keyloggers&#039;&#039;&#039;:&lt;br /&gt;
** &#039;&#039;&#039;Stay up-to-date&#039;&#039;&#039;. Make sure you consistently update your devices and software to help prevent malware.&lt;br /&gt;
** &#039;&#039;&#039;Anti-malware&#039;&#039;&#039;. Use anti-malware software. Yes, [https://www.clamav.net/ Linux has anti-malware software too]. No, [https://madaidans-insecurities.github.io/linux.html using Linux does not mean you are secure].&lt;br /&gt;
** &#039;&#039;&#039;Sandboxing and mandatory access control&#039;&#039;&#039;. You should probably sandbox applications and apply mandatory access control. This will depend on your OS.&lt;br /&gt;
** &#039;&#039;&#039;Don’t download dodgy stuff&#039;&#039;&#039;. Pretty self explanatory really.&lt;br /&gt;
&lt;br /&gt;
Following the common themes: you should [[#use-passkeys|use passkeys]] &#039;&#039;instead of&#039;&#039; passwords where possible. Where it isn’t possible, [[#use-a-password-manager|use a password manager]]. Wherever possible, [[2FA|use 2FA]].&lt;br /&gt;
&lt;br /&gt;
== Use Passkeys ==&lt;br /&gt;
&lt;br /&gt;
Passkeys are a more secure alternative to passwords. You don’t need to remember them as they are created and stored safely by software on your device (or hardware with a security key). Passkeys are also [https://www.microsoft.com/en-us/security/blog/2024/12/12/convincing-a-billion-users-to-love-passkeys-ux-design-insights-from-microsoft-to-boost-adoption-and-security/ a &#039;&#039;lot&#039;&#039; faster to use].&lt;br /&gt;
&lt;br /&gt;
=== How Do They Work? ===&lt;br /&gt;
&lt;br /&gt;
They essentially replace your password with public-key cryptography&amp;lt;ref&amp;gt;this is a very simple explanation that misses out on a lot of detail. For a nicer explanation, see [https://www.youtube.com/watch?v=xYfiOnufBSk this computerphile video]. For a more in-depth explanation, see [https://www.youtube.com/watch?v=V-7zMIgGO1U this conference talk], or [https://www.youtube.com/watch?v=sCybe9FAGOc this goto; conference talk].&amp;lt;/ref&amp;gt;. You securely store a private key for each account and give the server a public key. The private key is something only you should have access to, whereas anyone can see your public key. To log in, the server creates a challenge using your public key which can only be solved with the private key. Since you should be the only person with the private key, the server knows it must be you.&lt;br /&gt;
&lt;br /&gt;
In the event of a breach, the public key being leaked doesn’t matter because it is public by design. This is another advantage over passwords&amp;lt;ref&amp;gt;unless a [https://en.wikipedia.org/wiki/Password-authenticated_key_agreement PAKE] is in use.&amp;lt;/ref&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Usage ===&lt;br /&gt;
&lt;br /&gt;
To use passkeys, we’d recommend either installing an extension for your password manager (if the password manager supports passkeys), or, if you have money to spend, buy a security key and use that instead (YubiKeys are pretty common).&lt;br /&gt;
&lt;br /&gt;
If your browser is up to date, that’s all you really need to do. Whenever you sign up for a new account, navigate to whatever security settings that service has and add a passkey. Make sure it uses your password manager extension to do so. Unfortunately, adoption has been slow, so many accounts will still require a password. It is therefore important to [[#use-a-password-manager|use a password manager]] effectively.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;tip&amp;quot;&amp;gt;&lt;br /&gt;
We do not recommend storing passkeys on your device (i.e. in the browser or your phone). If you reset or lose your device, your passkeys will be lost too. Using a service like Bitwarden means your private keys are synced between devices.&lt;br /&gt;
&lt;br /&gt;
With physical security keys, we recommend buying two and making them alike where possible. Keep the backup somewhere safe and your main key attached to something that makes it hard to lose.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Use a Password Manager ==&lt;br /&gt;
&lt;br /&gt;
Our general guidance for effective use of a password manager is as follows:&lt;br /&gt;
&lt;br /&gt;
* For the master password, use a long passphrase consisting of five or so words, with special character word separators, and added numbers. This is something you will memorise, so make sure it is memorable, but avoid using personal information (such as birthday, your pets’ names, etc.).&lt;br /&gt;
* Enable 2FA. For a 2FA app, use something sensible. We’d recommend you find an open source one such as [https://ente.com/auth/ ente auth]. For more information on 2FA see [[2FA|the 2FA Wiki page]].&lt;br /&gt;
* Use the browser extension and app for your password manager. This should give you stuff like auto-fill, password generation, and should make saving new accounts easier.&lt;br /&gt;
* If using VaultTub or Bitwarden, follow the recommended settings on [[VaultTub|the VaultTub Wiki page]].&lt;br /&gt;
* When generating passphrases:&lt;br /&gt;
** Set the number of words to something [https://www.ncsc.gov.uk/blog-post/three-random-words-or-thinkrandom-0 greater than or equal to three]. Optionally, change this every so often.&lt;br /&gt;
** Enable adding a number.&lt;br /&gt;
** Optionally, cycle through word separators every so often.&lt;br /&gt;
** Optionally, cycle between using and not using capitalisation every so often.&lt;br /&gt;
** Save them to your password manager immediately after generation. This will save you from making an account, realising you didn’t save the passphrase, and then having to immediately reset your password.&lt;br /&gt;
* You don’t need to bother with rotating your master passphrase unless you have suspicion of a breach.&lt;br /&gt;
&lt;br /&gt;
== External Resources ==&lt;br /&gt;
&lt;br /&gt;
* [https://www.ncsc.gov.uk/section/advice-guidance/all-topics/passwords NCSC guidance]&lt;br /&gt;
* [https://www.ncsc.gov.uk/collection/passwords NCSC guidance for system owners]&lt;br /&gt;
* [https://www.nist.gov/cybersecurity/how-do-i-create-good-password NIST guidance]&lt;br /&gt;
* [https://en.wikipedia.org/wiki/Password_strength#Common_recommendations Wikipedia]&lt;br /&gt;
&lt;br /&gt;
-----&lt;br /&gt;
&lt;br /&gt;
&amp;lt;references /&amp;gt;&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=2FA&amp;diff=103</id>
		<title>2FA</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=2FA&amp;diff=103"/>
		<updated>2026-06-04T06:52:29Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Two-factor authentication is key to security, however if set up incorrectly can lead to worse security or just be unusable. Furthermore, you must know and set up backups for if you lose access to your phone.&lt;br /&gt;
&lt;br /&gt;
For all accounts it is highly recommended to setup 2FA when it is an option, but only in a way where it can be accessed by other people in the society easily.&lt;br /&gt;
&lt;br /&gt;
This document will go over the basics for all of the above.&lt;br /&gt;
&lt;br /&gt;
== TOTP ==&lt;br /&gt;
&lt;br /&gt;
TOTP or One Time Password/Passcode are the most common 2FA you will see as they can be easily setup on any device and are somewhat easy to understand.&lt;br /&gt;
&lt;br /&gt;
The most common form of this are codes which change every 30 seconds.&lt;br /&gt;
&lt;br /&gt;
=== Individuals ===&lt;br /&gt;
&lt;br /&gt;
The issue with TOTP is that normally only 1 device has access to the codes (however you can set multiple devices to this but it is hard to keep up to date).&lt;br /&gt;
&lt;br /&gt;
So for your own accounts you can mostly ignore the worry above and just download an app on your phone which handles this e.g:&lt;br /&gt;
&lt;br /&gt;
Apps which can help you store all your 2FAs:&lt;br /&gt;
&lt;br /&gt;
* Authy&lt;br /&gt;
* Google Authenticator&lt;br /&gt;
* Microsoft Authenticator&lt;br /&gt;
* Or any other that supports TOTP&lt;br /&gt;
&lt;br /&gt;
Note that some companies (cough cough Google and Microsoft) like to push you to download their authenticator app when setting up TOTP, this is not actually required because its an open standard, so please for your own sanity just use one app for this.&lt;br /&gt;
&lt;br /&gt;
=== Student-Led Initiatives ===&lt;br /&gt;
&lt;br /&gt;
For student-led initiatves, we need to make sure that in the future we can have someone else in the initiative to access and use the code.&lt;br /&gt;
&lt;br /&gt;
Therefore we recommend using [[../systems/vaulttub.md|our vaultwarden instance]], and storing it in the same place as the username and password (there is be a field called TOTP).&lt;br /&gt;
&lt;br /&gt;
More information can be found [https://bitwarden.com/help/authenticator-keys/ on Bitwarden Documentation].&lt;br /&gt;
&lt;br /&gt;
=== Adding a code ===&lt;br /&gt;
&lt;br /&gt;
Once you know where you are going to store your TOTP codes, adding one is really quite simple (though it may be hard to find).&lt;br /&gt;
&lt;br /&gt;
Note these instructions are generalised so you may need to use your intuition or guesswork to translate these into practical steps.&lt;br /&gt;
&lt;br /&gt;
* Find the 2FA section on the account settings page&lt;br /&gt;
** This may sometimes be under “password” or “security”&lt;br /&gt;
* You should then be able to click “Add 2FA” or “Add Authenticator App” or “Add TOTP”.&lt;br /&gt;
* If the website says to specifically download an app, ignore and press “continue”.&lt;br /&gt;
* Once you see a QR code, you need to open the app which will store the code:&lt;br /&gt;
** &#039;&#039;&#039;Most apps&#039;&#039;&#039;: click “Add” (you may need to then click “Other accounts” if they ask you to log in). This should bring up a camera where you can scan the QR code.&lt;br /&gt;
** &#039;&#039;&#039;Bitwarden&#039;&#039;&#039;: you will need to open the item with the login information and click edit. Then you can click the camera icon near TOTP (on the mobile version) which will allow you to then scan the QR code.&lt;br /&gt;
** &#039;&#039;&#039;Input Manually&#039;&#039;&#039;: If you can’t scan the QR code you, you can go back to the page and copy the code below the QR code (it may be hidden behind a menu which says “can’t scan?” or “input manually”). This code you can copy and paste into the TOTP section of your app.&lt;br /&gt;
* Once scanned, the app will show you a code and a count down (make sure you are looking at the correct one if there are multiple listed, it should be under the website name).&lt;br /&gt;
* You can then write the code in the input box of the website and hit continue (this checks you have set it up correctly).&lt;br /&gt;
* Some website will then show a set of backup codes, see [[#backup-codes|Backup Codes]] for more information, but you want to either download these and store them in a secure location or copy then into a Bitwarden note (separate from the original item).&lt;br /&gt;
&lt;br /&gt;
And that’s it!&lt;br /&gt;
&lt;br /&gt;
=== Using a code ===&lt;br /&gt;
&lt;br /&gt;
Using a code is really simple. When you log in to the website, it will ask for a code after entering the password.&lt;br /&gt;
&lt;br /&gt;
You just need to open up the app and copy and paste the code and hit enter!&lt;br /&gt;
&lt;br /&gt;
=== Backups ===&lt;br /&gt;
&lt;br /&gt;
Backing up 2FA codes is not particularly simple unless you copy and paste the manual code somewhere as it depends on your App.&lt;br /&gt;
&lt;br /&gt;
* Bitwarden: Your codes are not on your device so there’s no need.&lt;br /&gt;
* Google Authenticator: You can choose to backup your codes to your Google Drive, or you can export up to 10 accounts as a QR code which you can print and store as a hard copy.&lt;br /&gt;
* Authy + Microsoft Authenticator: I have no idea.&lt;br /&gt;
&lt;br /&gt;
== Backup Codes ==&lt;br /&gt;
&lt;br /&gt;
Backup codes are normally given after setting up 2FA as a fallback if you lose your 2FA device (so these should not be stored on the same device).&lt;br /&gt;
&lt;br /&gt;
Normally you should download and store in a folder on your computer (for added security encrypt the folder), or you can print them off and store them in a physical folder somewhere.&lt;br /&gt;
&lt;br /&gt;
For societies it has yet to be decided on the method, but ideally you should print them off and store them in the society locker.&lt;br /&gt;
&lt;br /&gt;
== Passkey ==&lt;br /&gt;
&lt;br /&gt;
Passkeys are relatively new and are basically extensions of the security keys, which allow you to sign in without using a password (and are often more secure than a password).&lt;br /&gt;
&lt;br /&gt;
Bitwarden has [https://bitwarden.com/passwordless-passkeys/ good documentation here about using bitwarden to store passkeys], which societies should be able to use.&lt;br /&gt;
&lt;br /&gt;
Personally, I would recommend people not to use these except through bitwarden unless you have a security key such as a yubikey, as if you lose or reset your phone, you cannot access your account.&lt;br /&gt;
&lt;br /&gt;
But if you have a Yubikey, use it everywhere, they are the most awesome thing ever created :)&lt;br /&gt;
&lt;br /&gt;
== 2FAs not to use ==&lt;br /&gt;
&lt;br /&gt;
The following 2FA methods are insecure and are often worse than a weak password and so &#039;&#039;&#039;should not be used&#039;&#039;&#039; at all cost (however some websites are still stupid and believe these are okay methods):&lt;br /&gt;
&lt;br /&gt;
* SMS&lt;br /&gt;
* Email&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
	<entry>
		<id>https://wiki.bathcs.com/index.php?title=SSH_keys&amp;diff=102</id>
		<title>SSH keys</title>
		<link rel="alternate" type="text/html" href="https://wiki.bathcs.com/index.php?title=SSH_keys&amp;diff=102"/>
		<updated>2026-06-04T06:50:41Z</updated>

		<summary type="html">&lt;p&gt;Hw2210: Translate from boss.bathcs.com&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;SSH keys are used for proving that you are who you say you are and are used instead of passwords for SSHing into different machines (and so also used pushing and pulling from repositories).&lt;br /&gt;
&lt;br /&gt;
These work by using a public and private key. You store the private key on your computer and sign messages with this (so basically encrypts it). Then another computer will use the public key to decrypt and so validate that you and only you could have encrypted the message.&lt;br /&gt;
&lt;br /&gt;
== Creating a Key ==&lt;br /&gt;
&lt;br /&gt;
[https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent GitHub also has great documentation on creating a key here]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Open a terminal ( [https://apps.microsoft.com/detail/9N0DX20HK701 Windows terminal], Powershell or &amp;lt;code&amp;gt;cmd&amp;lt;/code&amp;gt; on Windows)&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Run &amp;lt;code&amp;gt;ssh-keygen -t ed25519 -b 4096&amp;lt;/code&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{Note|text=&lt;br /&gt;
This creates a public and private key pair in your &amp;lt;code&amp;gt;.ssh&amp;lt;/code&amp;gt; folder using &amp;lt;code&amp;gt;ed25519&amp;lt;/code&amp;gt; encryption and 4096 bit long key. If you wish you can change the encryption type by replacing &amp;lt;code&amp;gt;ed25519&amp;lt;/code&amp;gt; with something else e.g. &amp;lt;code&amp;gt;rsa&amp;lt;/code&amp;gt; (which is for legacy systems)&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;Accept all the defaults&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;You may be asked for a password. It is good practice to set one. This encrypts the private key with this password and so you will have to type in the password when trying to SSH into another machine for the first time in your current session. (So remember it or write it in a password manager)&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;li&amp;gt;&amp;lt;p&amp;gt;This should save a private and public key in &amp;lt;code&amp;gt;.ssh&amp;lt;/code&amp;gt; in your home directory. Private key &amp;lt;code&amp;gt;~/.ssh/id_rsa&amp;lt;/code&amp;gt; and public key &amp;lt;code&amp;gt;~/.ssh/id_rsa.pub&amp;lt;/code&amp;gt;.&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;On windows we recommend navigating to this directory and check “show hidden files” and “show file extensions” - look it up if its not obvious (which it is not). You should be able to tell the difference between &amp;lt;code&amp;gt;id_rsa&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;id_rsa.pub&amp;lt;/code&amp;gt;. Windows will think the &amp;lt;code&amp;gt;.pub&amp;lt;/code&amp;gt; is a publisher file (it is not) and so open it with notepad.&amp;lt;/p&amp;gt;&amp;lt;/li&amp;gt;&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can then use this public key in locations such as “SSH keys” in GitLab and GitHub for security.&lt;/div&gt;</summary>
		<author><name>Hw2210</name></author>
	</entry>
</feed>