Example: Adding Database Configuration Options
In this tutorial, we'll explore ways to give your end user the option to either embed a database instance with the application, or connect your application to an external database instance that they will manage. We'll use a PostgreSQL database as an example, configuring an example app to connect.
This tutorial explores advanced topics like workload coordination, credential management, and refactoring your application's user-facing configuration in the Replicated Admin Console. We'll also review best practices for integrating persistent stores like databases, queues, and caches.
It is split into 5 sections:
- The Example Application
 - User-Facing Configuration
 - Embedding a Database
 - Connecting to an External Database
 
Prerequisites
This guide assumes you have:
- A running instance of the Replicated Admin Console (
kotsadm) to iterate against in either an existing cluster or an embedded cluster created with Replicated kURL. - A local git checkout of your application manifests.
 
Accompanying Code Examples
A full example of the code for this guide can be found in the kotsapps repository.
The Example Application
For demonstration purposes, we'll use a simple app that connects to a Postgres database via the psql CLI.
Once you've finished this guide, you should feel confident replacing it with any Kubernetes workload(s) that need to connect to a database.
The deployment we'll use can be seen below:
# pg-consumer.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pg-consumer
spec:
  selector:
    matchLabels:
      app: pg-consumer
  template:
    metadata:
      labels:
        app: pg-consumer
    spec:
      containers:
        - name: pg-consumer
          image: postgres:10
          # connect to the database every 20 seconds
          command:
            - /bin/sh
            - -ec
            - |
              while :; do
                 sleep 20
                 PGPASSWORD=${DB_PASSWORD} \
                 psql --host ${DB_HOST} \
                      --port ${DB_PORT} \
                      --user ${DB_USER} \
                      --dbname ${DB_NAME} \
                      --command 'SELECT NOW()'
              done
          # hard coded for now, we'll wire these up later
          env:
            - name: DB_HOST
              value: postgres
            - name: DB_PORT
              value: "5432"
            - name: DB_USER
              value: postgres
            - name: DB_PASSWORD
              value: postgres
            - name: DB_NAME
              value: postgres
This app simply connects to the database every 20 seconds and writes the server timestamp to stdout.
Even though psql supports default environment variables for host, username, etc that can be read transparently, we're intentionally using these generic DB_ variables for clarity.
Later, you can change these environment variable names to whatever format your application consumes.
For now we'll hard code the DB variable values, in the next sections we'll wire these up to the user-provided configuration.
Deploying the example application
Once you've added this deployment to you application's manifests directory, create a release by running replicated release create --auto locally.
Then head to the Admin Console instance and click Check for Updates on the Version History tab to pull the new release:

Click Deploy. You should be able to review the logs and see deployment.apps/pg-consumer created in applyStdout:

After it is deployed, you can run kubectl get pods to inspect the cluster.
We should expect the Pod to be crashlooping at this point, since there's no database to connect to just yet:
$ kubectl get pod
NAME                               READY   STATUS             RESTARTS   AGE
kotsadm-5bbf54df86-p7kqg           1/1     Running            0          12m
kotsadm-api-cbccb97ff-b6qxp        1/1     Running            2          12m
kotsadm-minio-0                    1/1     Running            0          12m
kotsadm-operator-84477b5c4-tplcp   1/1     Running            0          12m
kotsadm-postgres-0                 1/1     Running            0          12m
pg-consumer-75f49bfb69-mljr6       0/1     CrashLoopBackOff   1          10s
Checking the logs, we should see a connect error:
$ kubectl logs -l app=pg-consumer
psql: could not translate host name "postgres" to address: Name or service not known
If the kubectl logs command hangs, you can try using the --previous flag to fetch the logs of the most recent crash:
$ kubectl logs -l app=pg-consumer --previous
psql: could not translate host name "postgres" to address: Name or service not known
Now that our test app is deployed, we'll walk through presenting options to the end user for connecting a Postgres instance to this app.
User-Facing Configuration
The core of this guide will be around how to give your end users the option to do one of the following actions:
- Bring their own PostgreSQL instance for your app to connect to
 - Use an "embedded" database bundled in with the application
 
The first step here is to present that option to the user, then we'll walk through implementing each scenario.
The kots.io/v1beta1 Config resource controls what configuration options are presented to the end user.
If you followed one of the "Getting Started" guides, you probably have a config.yaml in your manifests that looks something like the following YAML file:
apiVersion: kots.io/v1beta1
kind: Config
metadata:
  name: config-sample
spec:
  groups:
    - name: example_settings
      title: My Example Config
      description: Configuration to serve as an example for creating your own. See [https://kots.io/reference/v1beta1/config/](https://kots.io/reference/v1beta1/config/) for configuration docs. In this case, we provide example fields for configuring an Ingress object.
      items:
        - name: use_ingress
          title: Use Ingress?
          help_text: An example field to toggle inclusion of an Ingress Object
          type: bool
          default: "0"
        - name: ingress_hostname
          title: Ingress Hostname
          help_text: If desired, enter the hostname for ingress to this application. You can enter the IP of this instance, or a DNS hostname.
          type: text
          when: repl{{ ConfigOptionEquals "use_ingress" "1" }}
To add a database section, we'll modify it to include some database settings. In this case we'll remove the Ingress toggle that is included as an example, although you might also choose to leave this in. None of these database settings will have any effect yet, but we'll still be able to preview what the end user will see. Modify your YAML to include this database section:
apiVersion: kots.io/v1beta1
kind: Config
metadata:
  name: config-sample
spec:
  groups:
    - name: database
      title: Database
      items:
        - name: postgres_type
          help_text: Would you like to use an embedded postgres instance, or connect to an external instance that you manage?
          type: radio
          title: Postgres
          default: embedded_postgres
          items:
            - name: embedded_postgres
              title: Embedded Postgres
            - name: external_postgres
              title: External Postgres
        - name: embedded_postgres_password
          hidden: true
          type: password
          value: "{{repl RandomString 32}}"
This creates a toggle to allow the user to choose between an embedded or external Postgres instance, and a hidden field to generate a unique password for the embedded instance.
As mentioned in the introduction, a full example of the code for this guide can be found in the kotsapps repository.
Validating Config Changes
Even though the options aren't wired, let's create a new release to validate the configuration screen was modified.
Create a release by running replicated release create --auto.
Then head to the Admin Console instance and click Check for Updates on the Version History tab to pull the new release:

After the update is deployed, click the Config tab and review our new toggle. You might also notice that we've removed the Ingress settings to simplify things for this guide:

Now that we have the configuration screen started, we can proceed to implement the "Embedded Postgres" option.
Embedding a Database
To implement the embedded Database option, we'll add a Kubernetes Statefulset, and use the annotations for optional resources to control when it will be included in the application.
Adding the Secret and StatefulSet
First, we'll create a secret to store the root password for our embedded postgres instance:
# postgres-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: postgres
data:
  DB_PASSWORD: '{{repl ConfigOption "embedded_postgres_password" | Base64Encode }}'
Next, create a new YAML file in your manifests directory with the following contents.
Note the use of kots.io/when to only conditionally include this based on end-user inputs:
# postgres-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  labels:
    app: pg-provider
  annotations:
    kots.io/when: '{{repl ConfigOptionEquals "postgres_type" "embedded_postgres" }}'
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pg-provider
  serviceName: postgres
  template:
    metadata:
      labels:
        app: pg-provider
    spec:
      containers:
      - env:
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        # create a db called "postgres"
        - name: POSTGRES_DB
          value: postgres
        # create admin user with name "postgres"
        - name: POSTGRES_USER
          value: postgres
        # use admin password from secret
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              key: DB_PASSWORD
              name: postgres
        image: postgres:10
        name: postgres
        volumeMounts:
        - mountPath: /var/lib/postgresql/data
          name: pgdata
      volumes:
      - name: pgdata
        persistentVolumeClaim:
          claimName: pgdata
  volumeClaimTemplates:
  - metadata:
      name: pgdata
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi
Finally, lets add a Service object so we can route traffic to our postgres instance, again using kots.io/when to conditionally include this resource:
# postgres-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: postgres
  labels:
    app: pg-provider
  annotations:
    kots.io/when: '{{repl ConfigOptionEquals "postgres_type" "embedded_postgres" }}'
spec:
  ports:
  - port: 5432
  selector:
    app: pg-provider
  type: ClusterIP
Validating the embedded Database
After you've added these resources, you can push a new release and update in the Admin Console. You should see the following in the deployment logs:

We should now see an instance of Postgres running in our namespace as well. The consumer may still be crashlooping, but we can see the error is different now:
$ kubectl logs -l app=pg-consumer
psql: FATAL:  password authentication failed for user "postgres"
This is because we still need to deliver the generated password to our workload pod.
In pg-consumer.yaml, we'll remove this section:
            - name: DB_PASSWORD
              value: postgres
and replace it with:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_PASSWORD
The full Deployment should now look like the following YAML file:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pg-consumer
spec:
  selector:
    matchLabels:
      app: pg-consumer
  template:
    metadata:
      labels:
        app: pg-consumer
    spec:
      containers:
        - name: pg-consumer
          image: 'postgres:10'
          # connect to the database every 20 seconds
          command:
            - /bin/sh
            - -ec
            - |
              while :; do
                 sleep 20
                 PGPASSWORD=${DB_PASSWORD} \
                 psql --host ${DB_HOST} \
                      --port ${DB_PORT} \
                      --user ${DB_USER} \
                      --dbname ${DB_NAME} \
                      --command 'SELECT NOW()'
              done
          # hard coded for now, we'll wire these up later
          env:
            - name: DB_HOST
              value: postgres
            - name: DB_PORT
              value: "5432"
            - name: DB_USER
              value: postgres
            - name: DB_NAME
              value: postgres
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_PASSWORD
From here, make another release and deploy it. You should see the consumer pod is now able to connect to the database:
$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
kotsadm-5bbf54df86-p7kqg           1/1     Running   0          144m
kotsadm-api-cbccb97ff-b6qxp        1/1     Running   2          144m
kotsadm-minio-0                    1/1     Running   0          144m
kotsadm-operator-84477b5c4-tplcp   1/1     Running   0          144m
kotsadm-postgres-0                 1/1     Running   0          144m
pg-consumer-77b868d7d8-xdn9v       1/1     Running   0          20s
postgres-0                         1/1     Running   0          6m22s
Checking the logs, we can connect now:
$ kubectl logs -l app=pg-consumer
              now
-------------------------------
 2020-04-12 17:11:45.019293+00
(1 row)
              now
-------------------------------
 2020-04-12 17:11:55.072041+00
(1 row)
Now that we've configured our application to read from an embedded postgres instance, we'll switch to allowing the end user to provide their own database connection parameters.
Connecting to an External Database
In this section, we'll expand our configuration section to allow end users to bring their own Postgres instance.
Modifying the Config Screen
Let's update our config screen to allow an end user to input some details about their database.
We'll add the following YAML, noting the use of the when field to conditionally hide or show fields in the user-facing config screen:
        - name: external_postgres_host
          title: Postgres Host
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: text
          default: postgres
        - name: external_postgres_port
          title: Postgres Port
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: text
          default: "5432"
        - name: external_postgres_user
          title: Postgres Username
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: text
          required: true
        - name: external_postgres_password
          title: Postgres Password
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: password
          required: true
        - name: external_postgres_db
          title: Postgres Database
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: text
          default: sentry
Your full configuration screen should now look something like the following YAMl file:
apiVersion: kots.io/v1beta1
kind: Config
metadata:
  name: config-sample
spec:
  groups:
    - name: database
      title: Database
      items:
        - name: postgres_type
          help_text: Would you like to use an embedded postgres instance, or connect to an external instance that you manage?
          type: radio
          title: Postgres
          default: embedded_postgres
          items:
            - name: embedded_postgres
              title: Embedded Postgres
            - name: external_postgres
              title: External Postgres
        - name: embedded_postgres_password
          hidden: true
          type: password
          value: "{{repl RandomString 32}}"
        - name: external_postgres_host
          title: Postgres Host
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: text
          default: postgres
        - name: external_postgres_port
          title: Postgres Port
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: text
          default: "5432"
        - name: external_postgres_user
          title: Postgres Username
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: text
          required: true
        - name: external_postgres_password
          title: Postgres Password
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: password
          required: true
        - name: external_postgres_db
          title: Postgres Database
          when: '{{repl ConfigOptionEquals "postgres_type" "external_postgres"}}'
          type: text
          default: postgres
Let's save this and create a new release. After deploying the release in the Admin Console, click Config and set the toggle to "External Postgres" to see the new fields:
In order to demonstrate that these are working, let's add some values that we know won't work, and just check to confirm that checking "External Postgres" will remove our embedded postgres instance:

Save these settings, and then you'll be directed back to the Version History page to apply the change:

after this is deployed, we should see that the postgres statefulset has been removed, and that our sample application is back to failing:
$ kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
kotsadm-5bbf54df86-8ws98           1/1     Running   0          12m
kotsadm-api-cbccb97ff-r7mz6        1/1     Running   2          12m
kotsadm-minio-0                    1/1     Running   0          12m
kotsadm-operator-84477b5c4-4gmbm   1/1     Running   0          12m
kotsadm-postgres-0                 1/1     Running   0          12m
pg-consumer-6bd78594d-n7nmw        0/1     Error     2          29s
You'll note that it is failing, but it is still using our hardcoded environment variables, not the user-entered config. In the next step, we'll wire the end-user configuration values into our service.
$ kubectl logs -l app=pg-consumer
psql: could not translate host name "postgres" to address: Name or service not known
Mapping User Inputs
To map the user-supplied configuration, we'll start by expanding our secret we created before, adding fields for additional variables, using {{repl if ... }} blocks to switch between embedded/external contexts.
To start, you can add a field for hostname, using Base64Encode. You must use a single line, as shown in the following example.
apiVersion: v1
kind: Secret
metadata:
  name: postgres
data:
  DB_PASSWORD: '{{repl ConfigOption "embedded_postgres_password" | Base64Encode }}'
  DB_HOST:
    {{repl if ConfigOptionEquals "postgres_type" "embedded_postgres" }}{{repl Base64Encode "postgres" }}{{repl else}}{{repl ConfigOption"external_postgres_host" | Base64Encode }}{{repl end}}
Now that we have the value in our Secret, we can modify our deployment to consume it. Replace this text:
            - name: DB_HOST
              value: postgres
with this text:
            - name: DB_HOST
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_HOST
Your full deployment should look something like the following YAML file:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pg-consumer
spec:
  selector:
    matchLabels:
      app: pg-consumer
  template:
    metadata:
      labels:
        app: pg-consumer
    spec:
      containers:
        - name: pg-consumer
          image: 'postgres:10'
          # connect to the database every 20 seconds
          command:
            - /bin/sh
            - -ec
            - |
              while :; do
                 sleep 20
                 PGPASSWORD=${DB_PASSWORD} \
                 psql --host ${DB_HOST} \
                      --port ${DB_PORT} \
                      --user ${DB_USER} \
                      --dbname ${DB_NAME} \
                      --command 'SELECT NOW()'
              done
          env:
            - name: DB_HOST
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_HOST
            - name: DB_PORT
              value: "5432"
            - name: DB_USER
              value: postgres
            - name: DB_NAME
              value: postgres
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_PASSWORD
From here, let's create and deploy a release, and verify that the secret has the customer-provided value, base64 decoding the secret contents:
$ kubectl get secret postgres -o yaml | head -n 4
apiVersion: v1
data:
  DB_HOST: ZmFrZQ==
  DB_PASSWORD: ajNVWDd1RnRfc0NkVTJqOFU3Q25xUkxRQk5fUlh3RjA=
You can verify we pulled in our user-provided config by base64-decoding the DB_HOST field:
$ echo ZmFrZQ== | base64 --decode
fake
Checking on our service itself, we can verify that it's now trying to connect to the fake hostname instead of postgres:
$ kubectl logs -l app=pg-consumer
psql: could not translate host name "fake" to address: Name or service not known
We'll optionally wire this to a real external Postgres database later, but for now we'll proceed to add the rest of the fields.
Extending this to All Fields
Now that we've wired the DB_HOST field all the way through, we'll do the same for the other fields. In the end, your Secret and Deployment should look like the following YAML files:
# postgres-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: postgres
data:
  DB_HOST: >-
    {{repl if ConfigOptionEquals "postgres_type" "embedded_postgres" -}}
      {{repl Base64Encode "postgres" }}
    {{repl else -}}
      {{repl ConfigOption "external_postgres_host" | Base64Encode }}
    {{repl end}}
  DB_PORT: >-
    {{repl if ConfigOptionEquals "postgres_type" "embedded_postgres" -}}
      {{repl Base64Encode "5432" }}
    {{repl else -}}
      {{repl ConfigOption "external_postgres_port" | Base64Encode }}
    {{repl end}}
  DB_USER: >-
    {{repl if ConfigOptionEquals "postgres_type" "embedded_postgres" -}}
      {{repl Base64Encode "postgres" }}
    {{repl else -}}
      {{repl ConfigOption "external_postgres_user" | Base64Encode }}
    {{repl end}}
  DB_PASSWORD: >-
    {{repl if ConfigOptionEquals "postgres_type" "embedded_postgres" -}}
      {{repl ConfigOption "embedded_postgres_password" | Base64Encode }}
    {{repl else -}}
      {{repl ConfigOption "external_postgres_password" | Base64Encode }}
    {{repl end}}
  DB_NAME: >-
    {{repl if ConfigOptionEquals "postgres_type" "embedded_postgres" -}}
      {{repl Base64Encode "postgres" }}
    {{repl else -}}
      {{repl ConfigOption "external_postgres_db" | Base64Encode }}
    {{repl end}}
# pg-consumer.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pg-consumer
spec:
  selector:
    matchLabels:
      app: pg-consumer
  template:
    metadata:
      labels:
        app: pg-consumer
    spec:
      containers:
        - name: pg-consumer
          image: 'postgres:10'
          # connect to the database every 20 seconds
          command:
            - /bin/sh
            - -ec
            - |
              while :; do
                 sleep 20
                 PGPASSWORD=${DB_PASSWORD} \
                 psql --host ${DB_HOST} \
                      --port ${DB_PORT} \
                      --user ${DB_USER} \
                      --dbname ${DB_NAME} \
                      --command 'SELECT NOW()'
              done
          env:
            - name: DB_HOST
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_HOST
            - name: DB_PORT
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_PORT
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_USER
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_PASSWORD
            - name: DB_NAME
              valueFrom:
                secretKeyRef:
                  name: postgres
                  key: DB_NAME
Optionally, you can be extra concise and collapse each individual env valueFrom into a single envFrom secretRef entry:
# pg-consumer.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pg-consumer
spec:
  selector:
    matchLabels:
      app: pg-consumer
  template:
    metadata:
      labels:
        app: pg-consumer
    spec:
      containers:
        - name: pg-consumer
          image: 'postgres:10'
          # connect to the database every 20 seconds
          command:
            - /bin/sh
            - -ec
            - |
              while :; do
                 sleep 20
                 PGPASSWORD=${DB_PASSWORD} \
                 psql --host ${DB_HOST} \
                      --port ${DB_PORT} \
                      --user ${DB_USER} \
                      --dbname ${DB_NAME} \
                      --command 'SELECT NOW()'
              done
          envFrom:
          - secretRef:
              name: postgres
After deploying this, you should see all of the fields in the secret:
$ kubectl get secret postgres -o yaml
apiVersion: v1
data:
  DB_HOST: ZmFrZQ==
  DB_NAME: ZmFrZQ==
  DB_PASSWORD: ZXh0cmEgZmFrZQ==
  DB_PORT: NTQzMjE=
  DB_USER: ZmFrZQ==
kind: Secret
# ...snip...
We can also print the environment in our sample app to verify that all of the values are piped properly:
$ kubectl exec $(kubectl get pod -l app=pg-consumer -o jsonpath='{.items[0].metadata.name}' ) -- /bin/sh -c 'printenv | grep DB_'
DB_PORT=54321
DB_NAME=fake
DB_PASSWORD=extra fake
DB_HOST=fake
DB_USER=fake
Testing Config Changes
Now let's make some changes to the database credentials. In this case, we'll use a Postgres database provisioned in Amazon RDS, but you can use any external database. To start, head to the "Config" screen and input your values:

Let's save and apply this config and check in our pod again:
$ kubectl exec $(kubectl get pod -l app=pg-consumer -o jsonpath='{.items[0].metadata.name}' ) -- /bin/sh -c 'printenv | grep DB_'
DB_PORT=54321
DB_NAME=fake
DB_PASSWORD=extra fake
DB_HOST=fake
DB_USER=fake
Uh oh, It appears that our values did not get updated! If you've worked with Secrets before, you may know that there's a long-standing issue in Kubernetes where pods that load config from Secrets or ConfigMaps won't automatically restart when underlying config is changed. There are some tricks to make this work, and in the next step we'll implement one of them, but for now we can delete the pod to verify that the configuration is being piped through to our sample application:
$ kubectl delete pod -l app=pg-consumer
pod "pg-consumer-6df9d5d7fd-bd5z6"" deleted
If the pod is crashlooping, you might need to add --force --grace-period 0 to force delete it.
In either case, once a new pod starts, we should now see it loading the correct config:
$ kubectl exec $(kubectl get pod -l app=pg-consumer -o jsonpath='{.items[0].metadata.name}' ) -- /bin/sh -c 'printenv | grep DB_'
DB_PORT=5432
DB_NAME=postgres
DB_PASSWORD=<redacted>
DB_HOST=10.128.0.12
DB_USER=postgres
Triggering Restarts on Changes
In order to automate this restart on changes, we're going to use a hash of all database parameters to trigger a rolling update whenever database parameters are changed.
We'll use a hidden, readonly field to store this in our config screen:
        - name: external_postgres_confighash
          hidden: true
          readonly: true
          type: text
          value: '{{repl (sha256sum (print (ConfigOption "external_postgres_host") (ConfigOption "external_postgres_port") (ConfigOption "external_postgres_user") (ConfigOption "external_postgres_password") (ConfigOption "external_postgres_db") ))}}'
The hidden flag will hide it from the UI, and the readonly flag in this case will cause the value to be re-computed any time an upstream ConfigOption value changes.
Next, let's add this as an annotation to our deployment's pod template at spec.template.metadata.annotations:
annotations:
  kots.io/config-hash: '{{repl ConfigOption "external_postgres_confighash"}}'
Note: It's worth noting here that there's nothing special about the kots.io/config-hash annotation. We could have just as easily called this annotation my-app-something-fake instead.
What matters here is that when the value in a Deployment annotation changes, it will cause Kubernetes to roll out a new version of the pod, stopping the old one and thus picking up our config changes.
Your full deployment should now look like the following YAML file:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pg-consumer
spec:
  selector:
    matchLabels:
      app: pg-consumer
  template:
    metadata:
      labels:
        app: pg-consumer
      annotations:
        kots.io/config-hash: '{{repl ConfigOption "external_postgres_confighash"}}'
    spec:
      containers:
        - name: pg-consumer
          image: 'postgres:10'
          # connect to the database every 20 seconds
          command:
            - /bin/sh
            - -ec
            - |
              while :; do
                 sleep 20
                 PGPASSWORD=${DB_PASSWORD} \
                 psql --host ${DB_HOST} \
                      --port ${DB_PORT} \
                      --user ${DB_USER} \
                      --dbname ${DB_NAME} \
                      --command 'SELECT NOW()'
              done
          envFrom:
            - secretRef:
                name: postgres
Integrating a Real Database
If you'd like at this point, you can integrate a real database in your environment, just fill out your configuration fields. You'll know you did it right if your pg-consumer pod can connect.