Open-Source Tools: Doku, Noisedash and RecipeSage

Published: Feb 1, 2024 by Isaac Johnson

Every now and then I like to explore some useful open-source smaller apps. On their own, they may not really warrant a full blog post, but hopefully you see something interesting in the following three: Doku, Noisedash and RecipeSage.

Doku

I became aware of Docku from a MariusHosting post. In that post, he leveraged Portainer to launch it on a NAS.

Let’s first just try locally to see what it is.

If you do not run in priveledged, expect an error:

$ docker run -p 9900:9090 --name dokutest amerkurev/doku
Unable to find image 'amerkurev/doku:latest' locally
latest: Pulling from amerkurev/doku
27cead1e48f5: Pull complete
47d5bd81c760: Pull complete
e4692b1d2d12: Pull complete
575ce9da186e: Pull complete
2ac00e010448: Pull complete
f4aa2870cc5c: Pull complete
6e3d39888231: Pull complete
cdaf8b327cab: Pull complete
abd17ce52993: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:145084d4d910877e1b0a2ce80da2caca1abbf5ba58ea732c589100b519c5d96b
Status: Downloaded newer image for amerkurev/doku:latest
doku v0.0.16-5eb6b55-20230220T18:49:35
time="2023-12-24T17:54:27Z" level=info msg="docker engine API (1.24)"
time="2023-12-24T17:54:27Z" level=info msg="calculating size of volumes..."
time="2023-12-24T17:54:27Z" level=info msg="starting http server at 0.0.0.0:9090"
time="2023-12-24T17:54:27Z" level=error msg="failed to get the list of containers" err="Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?"

You’ll want to mount the docker sock:

builder@DESKTOP-QADGF36:~$ docker rm dokutest
dokutest

builder@DESKTOP-QADGF36:~$ docker run -p 9900:9090 -v /var/run/docker.sock:/var/run/docker.sock:ro -v /:/hostroot:ro --name dokutest amerkurev/doku
doku v0.0.16-5eb6b55-20230220T18:49:35
time="2023-12-24T17:55:53Z" level=info msg="docker engine API (1.41)"
time="2023-12-24T17:55:53Z" level=info msg="calculating size of volumes..."
time="2023-12-24T17:55:53Z" level=info msg="starting http server at 0.0.0.0:9090"

I can see I’ve been a bit wasteful in images. I guess that is the consequence of being a blogging addict.

/content/images/2024/01/miscapps-01.png

Looking at one of the largest, “flamboyant_feynman”, I can see locally that is based on an 18mo old splunk image

$ docker ps -a | grep flamb
4099739a4d41   splunk/splunk:latest                                            "/sbin/entrypoint.sh…"   18 months ago   Exited (137) 18 months ago
                                                         flamboyant_feynman

I can also view images under the “Images” section as well with sortable columns

/content/images/2024/01/miscapps-02.png

If I remove a container

$ docker rm 4099739a4d41
4099739a4d41
$ docker rm flamboyant_feynman
Error: No such container: flamboyant_feynman

I can see it is reflected in the UI on refresh

/content/images/2024/01/miscapps-03.png

I can see other wasted space, like docker build caches which could be pruned

/content/images/2024/01/miscapps-04.png

Let’s say I want to prune everything older than two years (while 2y and 2Y failed, 24m worked):

$ docker image prune -a --filter "until=24m"
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] y
Deleted Images:
untagged: myrunner:latest
untagged: harbor.freshbrewed.science/freshbrewedprivate/myghrunner:1.1.0
untagged: harbor.freshbrewed.science/freshbrewedprivate/myghrunner@sha256:37edf935b9aa0c5e2911f8cd557841d82a65aa9ecf3f9824a0bbb291e11a2f25
deleted: sha256:16fb58a29b01e9b07ac2610fdfbf9ff3eb820026c1230e1214f7f34b49e7b506
untagged: pyplanelist:0.0.4
....snip...
deleted: sha256:ff1d7dfb73bf330b9f447c9cd71c9c6e8bb20821d1a1f253f3a20fc9cd168ebe
deleted: sha256:8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759

Total reclaimed space: 13.28GB

While I don’t see space reclaimed in the UI, i do see the absence of the older images

/content/images/2024/01/miscapps-05.png

A few things don’t work so well. It complains it lacks access to logs and the mounts seem to struggle with Windows locations

/content/images/2024/01/miscapps-06.png

Let’s move to Linux where my Dockerhost on 192.168.1.100/99 is really the area of concern.

builder@builder-T100:~$ docker run -d -p 9900:9090 -v /var/run/docker.sock:/var/run/docker.sock:ro -v /:/hostroot:ro --name dokutest amerkurev/doku
Unable to find image 'amerkurev/doku:latest' locally
latest: Pulling from amerkurev/doku
27cead1e48f5: Pull complete
47d5bd81c760: Pull complete
e4692b1d2d12: Pull complete
575ce9da186e: Pull complete
2ac00e010448: Pull complete
f4aa2870cc5c: Pull complete
6e3d39888231: Pull complete
cdaf8b327cab: Pull complete
abd17ce52993: Pull complete
4f4fb700ef54: Pull complete
Digest: sha256:145084d4d910877e1b0a2ce80da2caca1abbf5ba58ea732c589100b519c5d96b
Status: Downloaded newer image for amerkurev/doku:latest
c0c81ce72625b8b5fa9c9de62f59ef269f4ca179d7f16cda00ff740e810550f3

Which shows

/content/images/2024/01/miscapps-07.png

In the windows WSL instance, I got a blank page on “containers”, however here I see a few, including some that failed to run listed:

/content/images/2024/01/miscapps-08.png

Now, when I view Logs, I can see how much space is being consumed by logs, which I guess is the goal here - find waste.

/content/images/2024/01/miscapps-09.png

I should point out there is a dark mode (the little icon on the right of the headings):

/content/images/2024/01/miscapps-10.png

While I could externalize this with a Kubernetes externalEndpoint, I think it’s best for just my usage to debug when needed.

Just so you can see the subtle animations, a quick tour:

Noisedash

Another useful tool, that probably could just be an app as well, is Noisedash.

We’ll start, as before, with a quick docker run.

First, I’ll make a couple docker volumes it can use

builder@builder-T100:~$ docker volume create samples
samples
builder@builder-T100:~$ docker volume create noisedb
noisedb

Then I need to creaet a config.json. (default maxSampleSize is 10Gb in bytes)

builder@builder-T100:~/noisedash$ vi config.json
builder@builder-T100:~/noisedash$ cat ./config.json
{
  "Server": {
    "listeningPort": 1432,
    "sessionFileStorePath": "sessions",
    "sampleUploadPath": "samples",
    "maxSampleSize": 10737418240,
    "logFile": "log/noisedash.log",
    "tls": false,
    "tlsKey": "certs/key.pem",
    "tlsCert": "certs/cert.pem"
  }
}

Lastly, launch it

builder@builder-T100:~/noisedash$ docker run -d -p 1432:1432 -v noisedb:/var/noisedash/db -v samples:/var/noisedash/samples -v ./config.json:/var/noisedash/config/default.json --name noisedash noisedash/noisedash:latest
Unable to find image 'noisedash/noisedash:latest' locally
latest: Pulling from noisedash/noisedash
311da6c465ea: Pull complete
7e9bf114588c: Pull complete
ffd9397e94b7: Pull complete
513d77925604: Pull complete
ae3b95bbaa61: Pull complete
0e421f66aff4: Pull complete
ca266fd61921: Pull complete
ee7d78be1eb9: Pull complete
48dc19beb3d5: Pull complete
b84e61fa0d30: Pull complete
a8304f1e41a2: Pull complete
521c7274591d: Pull complete
8d6d264b3bff: Pull complete
Digest: sha256:7aab65f9c467a1c036f6ec8510f781afdd42af8b2acaf349ff4c41cebf78c945
Status: Downloaded newer image for noisedash/noisedash:latest
c5654c05d2fabc18211c7ea37fa00ff7a54a57fb0c3450aa68e8fe8da8fcda1b

Visiting the URL, I find I need to register first

/content/images/2024/01/miscapps-11.png

Then login

/content/images/2024/01/miscapps-12.png

The next part just doesn’t really translate. The microphone had a bit of a hiss, but you get the idea:

I think that’s pretty fun and worth exposing. I can either rewrite it as a Kubernetes native app OR expose through Docker using an external endpoint.

Regardless of which path, I need an A Record first

$ cat r53-noisedash.json
{
    "Comment": "CREATE noisedash fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "noisedash.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "75.73.224.240"
            }
          ]
        }
      }
    ]
  }

$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-noisedash.json
{
    "ChangeInfo": {
        "Id": "/change/C070071930KXLJ9OMLS4U",
        "Status": "PENDING",
        "SubmittedAt": "2023-12-25T14:19:27.111Z",
        "Comment": "CREATE noisedash fb.s A record "
    }
}

There is a quick trick to getting a docker-compose into Kubernetes YAML if you want to just get the job done fast: Use AI.

It is not perfect, but it gets you started:

/content/images/2024/01/miscapps-14.png

The problem with the generated AI YAML is it didn’t create PVCs for the storage.

I tend to just engage with Bing AI (or Copilot - pick a lane, Microsoft, pick a lane):

/content/images/2024/01/miscapps-15.png

This is actually pretty close:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: samples-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: noisedash
spec:
  replicas: 1
  selector:
    matchLabels:
      app: noisedash
  template:
    metadata:
      labels:
        app: noisedash
    spec:
      containers:
      - name: noisedash
        image: noisedash/noisedash:latest
        ports:
        - containerPort: 1432
        volumeMounts:
        - name: db
          mountPath: /var/noisedash/db
        - name: samples
          mountPath: /var/noisedash/samples
        - name: config
          mountPath: /var/noisedash/config
      volumes:
      - name: db
        persistentVolumeClaim:
          claimName: db-pvc
      - name: samples
        persistentVolumeClaim:
          claimName: samples-pvc
      - name: config
        hostPath:
          path: /path/to/your/config/default.json
---
apiVersion: v1
kind: Service
metadata:
  name: noisedash
spec:
  selector:
    app: noisedash
  ports:
    - protocol: TCP
      port: 80
      targetPort: 1432

I just need to add the Ingress at the end

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
  labels:
    app.kubernetes.io/instance: noisedash
  name: noisedashingress
spec:
  rules:
  - host: noisedash.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: noisedash
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - noisedash.freshbrewed.science
    secretName: noisedash-tls

Which I can now apply:

$ kubectl apply -f noiseall.yaml
persistentvolumeclaim/db-pvc created
persistentvolumeclaim/samples-pvc created
deployment.apps/noisedash created
service/noisedash created
ingress.networking.k8s.io/noisedashingress created

I realized we neglected to actually handle the settings/configmap

We need to add

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: noisedashcfg
data:
  config.json: |
    {
      "Server": {
        "listeningPort": 1432,
        "sessionFileStorePath": "sessions",
        "sampleUploadPath": "samples",
        "maxSampleSize": 10737418240,
        "logFile": "log/noisedash.log",
        "tls": false,
        "tlsKey": "certs/key.pem",
        "tlsCert": "certs/cert.pem"
      }
    }

Then change our deployment to use it

        - name: config
          mountPath: /var/noisedash/config/default.json
          subPath: config.jsson
...
        - name: config
            configMap:
            name: noisedashcfg

I deleted the pvcs and pod by hand (they generally don’t resize when using just a kubectl apply on existing PVCs)

noisedash-84c7747f4d-88zp9                               0/1     CrashLoopBackOff   6 (2m18s ago)     8m57s

$ kubectl delete pvc db-pvc &
[1] 4035 persistentvolumeclaim "db-pvc" deleted
$ kubectl delete pvc samples-pvc
persistentvolumeclaim "samples-pvc" deleted

$ kubectl delete pod noisedash-84c7747f4d-88zp9
pod "noisedash-84c7747f4d-88zp9" deleted

Then re-applied:

$ kubectl apply -f noiseall.yaml
persistentvolumeclaim/db-pvc created
persistentvolumeclaim/samples-pvc created
deployment.apps/noisedash configured
service/noisedash unchanged
configmap/noisedashcfg created
ingress.networking.k8s.io/noisedashingress unchanged

This time it’s running

$ kubectl get pods -l app=noisedash
NAME                        READY   STATUS    RESTARTS   AGE
noisedash-74f5697b5-t2lph   1/1     Running   0          112s

And I can sign in

/content/images/2024/01/miscapps-16.png

The website is now live at https://noisedash.freshbrewed.science/. Enjoy!

Also, a good OS person creates PRs, so I PR’ed my Manifest back.

Recipesage

RecipeSage is a “Collaborative Recipe Keeper, Meal Planner, and Shopping List Organizer” created by Julian Poy. There is a hosted option at Recipesage.com, but today we’ll explore the self-hosted option.

I’m going to start with the Docker compose version.

Since it (by default) runs on port 3000, I’ll stop gitness locally to free that up

t$ docker ps
CONTAINER ID   IMAGE                          COMMAND                  CREATED         STATUS                PORTS                              NAMES
20903d6f949e   pyplanelist:0.0.7              "python app.py"          2 days ago      Up 2 days             0.0.0.0:8999->80/tcp               pyplanetest
4b36604209fe   harness/gitness                "/app/gitness server"    2 months ago    Up 3 days             0.0.0.0:3000->3000/tcp, 3001/tcp   gitness
a613dc3843bd   ghcr.io/ajnart/homarr:latest   "docker-entrypoint.s…"   5 months ago    Up 3 days             0.0.0.0:7575->7575/tcp             homarr
cead637ea0a2   louislam/uptime-kuma:1         "/usr/bin/dumb-init …"   21 months ago   Up 3 days (healthy)   0.0.0.0:3001->3001/tcp             uptime-kuma
$ docker stop gitness
gitness

Then use Docker compose up:

$ git clone https://github.com/julianpoy/RecipeSage-selfhost.git
$ cd RecipeSage-selfhost
$ docker compose up -d
...
[+] Running 12/12
 ⠿ Network recipesage-selfhost_default         Created                                                                           0.6s
 ⠿ Volume "recipesage-selfhost_apimedia"       Created                                                                           0.0s
 ⠿ Volume "recipesage-selfhost_typesensedata"  Created                                                                           0.0s
 ⠿ Volume "recipesage-selfhost_postgresdata"   Created                                                                           0.0s
 ⠿ Container recipesage_browserless            Started                                                                           3.1s
 ⠿ Container recipesage_static                 Started                                                                           3.3s
 ⠿ Container recipesage_classifier             Started                                                                           2.3s
 ⠿ Container recipesage_pushpin                Started                                                                           2.5s
 ⠿ Container recipesage_typesense              Started                                                                           2.9s
 ⠿ Container recipesage_postgres               Started                                                                           3.1s
 ⠿ Container recipesage_api                    Started                                                                           2.9s
 ⠿ Container recipesage_proxy                  Started                                                                           4.1s

This actually launches quite a bit:

$ docker ps
CONTAINER ID   IMAGE                                               COMMAND                  CREATED          STATUS                PORTS                    NAMES
080a234ec2af   nginx                                               "/docker-entrypoint.…"   25 seconds ago   Up 20 seconds         0.0.0.0:80->80/tcp       recipesage_proxy
e327c7250b43   julianpoy/recipesage-selfhost:api-v2.12.10          "docker-entrypoint.s…"   25 seconds ago   Up 22 seconds                                  recipesage_api
6f3f3bac1f40   postgres:16                                         "docker-entrypoint.s…"   26 seconds ago   Up 23 seconds         5432/tcp                 recipesage_postgres
b88923067b29   julianpoy/ingredient-instruction-classifier:1.4.9   "docker-entrypoint.s…"   26 seconds ago   Up 23 seconds                                  recipesage_classifier
20f6294c28d8   browserless/chrome:1.57-puppeteer-19.2.2            "./start.sh"             26 seconds ago   Up 23 seconds         3000/tcp                 recipesage_browserless
888f8bf91307   julianpoy/pushpin:2023-09-17                        "/bin/sh -c 'sed -i …"   26 seconds ago   Up 23 seconds                                  recipesage_pushpin
7fbcd59b1517   typesense/typesense:0.24.1                          "/opt/typesense-serv…"   26 seconds ago   Up 23 seconds         8108/tcp                 recipesage_typesense
71698f9fc85b   julianpoy/recipesage-selfhost:static-v2.12.10       "/docker-entrypoint.…"   26 seconds ago   Up 22 seconds         80/tcp                   recipesage_static
20903d6f949e   pyplanelist:0.0.7                                   "python app.py"          2 days ago       Up 2 days             0.0.0.0:8999->80/tcp     pyplanetest
a613dc3843bd   ghcr.io/ajnart/homarr:latest                        "docker-entrypoint.s…"   5 months ago     Up 3 days             0.0.0.0:7575->7575/tcp   homarr
cead637ea0a2   louislam/uptime-kuma:1                              "/usr/bin/dumb-init …"   21 months ago    Up 3 days (healthy)   0.0.0.0:3001->3001/tcp   uptime-kuma

Since it’s running on Port 80, it’s easy to just check http://localhost

/content/images/2024/01/miscapps-17.png

I’ll create an account

/content/images/2024/01/miscapps-18.png

However… It just gave me an “error”

/content/images/2024/01/miscapps-19.png

I thought, perhaps just the account create didn’t respond with the right code… I’ll try logging in:

/content/images/2024/01/miscapps-20.png

but that too didn’t work.

I looked at the API logs and it seems database related

$ docker logs e327c7250b43
Database connection established
POST /users/register?false=false& 500 282.353 ms - 1033
Error
    at Query.run (/app/node_modules/sequelize/src/dialects/postgres/query.js:76:25)
    at /app/node_modules/sequelize/src/sequelize.js:650:28
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async PostgresQueryInterface.select (/app/node_modules/sequelize/src/dialects/abstract/query-interface.js:1001:12)
    at async Function.findAll (/app/node_modules/sequelize/src/model.js:1824:21)
    at async Function.findOne (/app/node_modules/sequelize/src/model.js:1998:12)
    at async /app/packages/backend/src/routes/users.js:643:20
    at async /app/node_modules/sequelize/src/sequelize.js:1196:18
    at async /app/packages/backend/src/routes/users.js:630:19
    at async /app/packages/backend/src/utils/wrapRequestWithErrorHandler.js:4:7

Looking at the database logs, it seems it wasn’t properly set up:

$ docker logs 6f3f3bac1f40
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/data ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting default time zone ... Etc/UTC
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
syncing data to disk ... ok


Success. You can now start the database server using:

    pg_ctl -D /var/lib/postgresql/data -l logfile start

initdb: warning: enabling "trust" authentication for local connections
initdb: hint: You can change this by editing pg_hba.conf or using the option -A, or --auth-local and --auth-host, the next time you run initdb.
waiting for server to start....2023-12-25 14:55:36.846 UTC [48] LOG:  starting PostgreSQL 16.1 (Debian 16.1-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
2023-12-25 14:55:36.848 UTC [48] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2023-12-25 14:55:36.861 UTC [51] LOG:  database system was shut down at 2023-12-25 14:55:36 UTC
2023-12-25 14:55:36.872 UTC [48] LOG:  database system is ready to accept connections
 done
server started
CREATE DATABASE


/usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*

waiting for server to shut down...2023-12-25 14:55:37.180 UTC [48] LOG:  received fast shutdown request
.2023-12-25 14:55:37.183 UTC [48] LOG:  aborting any active transactions
2023-12-25 14:55:37.185 UTC [48] LOG:  background worker "logical replication launcher" (PID 54) exited with exit code 1
2023-12-25 14:55:37.185 UTC [49] LOG:  shutting down
2023-12-25 14:55:37.188 UTC [49] LOG:  checkpoint starting: shutdown immediate
2023-12-25 14:55:37.317 UTC [49] LOG:  checkpoint complete: wrote 923 buffers (5.6%); 0 WAL file(s) added, 0 removed, 0 recycled; write=0.022 s, sync=0.102 s, total=0.132 s; sync files=301, longest=0.007 s, average=0.001 s; distance=4257 kB, estimate=4257 kB; lsn=0/1913088, redo lsn=0/1913088
2023-12-25 14:55:37.325 UTC [48] LOG:  database system is shut down
 done
server stopped

PostgreSQL init process complete; ready for start up.

2023-12-25 14:55:37.430 UTC [1] LOG:  starting PostgreSQL 16.1 (Debian 16.1-1.pgdg120+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
2023-12-25 14:55:37.436 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
2023-12-25 14:55:37.436 UTC [1] LOG:  listening on IPv6 address "::", port 5432
2023-12-25 14:55:37.442 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2023-12-25 14:55:37.448 UTC [64] LOG:  database system was shut down at 2023-12-25 14:55:37 UTC
2023-12-25 14:55:37.456 UTC [1] LOG:  database system is ready to accept connections
2023-12-25 14:58:26.712 UTC [77] ERROR:  relation "Users" does not exist at character 18
2023-12-25 14:58:26.712 UTC [77] STATEMENT:  SELECT "id" FROM "Users" AS "User" WHERE "User"."email" = 'isaac.johnson@gmail.com' LIMIT 1;
2023-12-25 14:58:36.195 UTC [77] ERROR:  relation "Users" does not exist at character 18
2023-12-25 14:58:36.195 UTC [77] STATEMENT:  SELECT "id" FROM "Users" AS "User" WHERE "User"."email" = 'isaac.johnson@gmail.com' LIMIT 1;
2023-12-25 14:59:13.362 UTC [81] ERROR:  relation "Users" does not exist at character 18
2023-12-25 14:59:13.362 UTC [81] STATEMENT:  SELECT "id" FROM "Users" AS "User" WHERE "User"."email" = 'isaac.johnson@gmail.com' LIMIT 1;
2023-12-25 14:59:17.767 UTC [81] ERROR:  relation "Users" does not exist at character 172
2023-12-25 14:59:17.767 UTC [81] STATEMENT:  SELECT "id", "name", "handle", "email", "passwordHash", "passwordSalt", "passwordVersion", "lastLogin", "stripeCustomerId", "enableProfile", "createdAt", "updatedAt" FROM "Users" AS "User" WHERE "User"."email" = 'isaac.johnson@gmail.com' LIMIT 1;
2023-12-25 14:59:25.749 UTC [81] ERROR:  relation "Users" does not exist at character 172
2023-12-25 14:59:25.749 UTC [81] STATEMENT:  SELECT "id", "name", "handle", "email", "passwordHash", "passwordSalt", "passwordVersion", "lastLogin", "stripeCustomerId", "enableProfile", "createdAt", "updatedAt" FROM "Users" AS "User" WHERE "User"."email" = 'isaac.johnson@gmail.com' LIMIT 1;
2023-12-25 14:59:34.422 UTC [81] ERROR:  relation "Users" does not exist at character 172
2023-12-25 14:59:34.422 UTC [81] STATEMENT:  SELECT "id", "name", "handle", "email", "passwordHash", "passwordSalt", "passwordVersion", "lastLogin", "stripeCustomerId", "enableProfile", "createdAt", "updatedAt" FROM "Users" AS "User" WHERE "User"."email" = 'isaac.johnson@gmail.com' LIMIT 1;
2023-12-25 15:00:37.529 UTC [62] LOG:  checkpoint starting: time
2023-12-25 15:00:41.771 UTC [62] LOG:  checkpoint complete: wrote 45 buffers (0.3%); 0 WAL file(s) added, 0 removed, 0 recycled; write=4.218 s, sync=0.014 s, total=4.243 s; sync files=12, longest=0.011 s, average=0.002 s; distance=261 kB, estimate=261 kB; lsn=0/19544F8, redo lsn=0/19544C0

I realized I missed a step!

Clearly in the docs, after docker compose, one must manually invoke a migrate

builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ ./migrate.sh
Performing migrations. This could take up to 1 minute.
{ event: 'migrating', name: '20180903174506-create-user.js' }
{
  event: 'migrated',
  name: '20180903174506-create-user.js',
  durationSeconds: 0.018
}
{ event: 'migrating', name: '20180903225610-create-session.js' }
{
  event: 'migrated',
  name: '20180903225610-create-session.js',
  durationSeconds: 0.011
}
{ event: 'migrating', name: '20180905031747-create-recipe.js' }
{
  event: 'migrated',
  name: '20180905031747-create-recipe.js',
  durationSeconds: 0.011
}
{ event: 'migrating', name: '20180905031929-create-message.js' }
{
  event: 'migrated',
  name: '20180905031929-create-message.js',
  durationSeconds: 0.012
}
{ event: 'migrating', name: '20180905040040-create-meal-plan.js' }
{
  event: 'migrated',
  name: '20180905040040-create-meal-plan.js',
  durationSeconds: 0.01
}
{ event: 'migrating', name: '20180905040042-create-meal-plan-item.js' }
{
  event: 'migrated',
  name: '20180905040042-create-meal-plan-item.js',
  durationSeconds: 0.011
}
{ event: 'migrating', name: '20180905040055-create-shopping-list.js' }
{
  event: 'migrated',
  name: '20180905040055-create-shopping-list.js',
  durationSeconds: 0.011
}
{
  event: 'migrating',
  name: '20180905040211-create-shopping-list-item.js'
}
{
  event: 'migrated',
  name: '20180905040211-create-shopping-list-item.js',
  durationSeconds: 0.011
}
{ event: 'migrating', name: '20180905042925-create-label.js' }
{
  event: 'migrated',
  name: '20180905042925-create-label.js',
  durationSeconds: 0.008
}
{ event: 'migrating', name: '20180905042940-create-recipe_label.js' }
{
  event: 'migrated',
  name: '20180905042940-create-recipe_label.js',
  durationSeconds: 0.011
}
{
  event: 'migrating',
  name: '20180909144458-create-meal-plan_collaborator.js'
}
{
  event: 'migrated',
  name: '20180909144458-create-meal-plan_collaborator.js',
  durationSeconds: 0.011
}
{
  event: 'migrating',
  name: '20180909150203-create-shopping-list_collaborator.js'
}
{
  event: 'migrated',
  name: '20180909150203-create-shopping-list_collaborator.js',
  durationSeconds: 0.011
}
{ event: 'migrating', name: '20180909194501-create-fcm-token.js' }
{
  event: 'migrated',
  name: '20180909194501-create-fcm-token.js',
  durationSeconds: 0.011
}
{ event: 'migrating', name: '20190318151614-add-recipe-indexedAt.js' }
{
  event: 'migrated',
  name: '20190318151614-add-recipe-indexedAt.js',
  durationSeconds: 0.005
}
{
  event: 'migrating',
  name: '20191020162730-add-user-stripeCustomerId.js'
}
{
  event: 'migrated',
  name: '20191020162730-add-user-stripeCustomerId.js',
  durationSeconds: 0.008
}
{ event: 'migrating', name: '20191020164936-create-stripe-payment.js' }
{
  event: 'migrated',
  name: '20191020164936-create-stripe-payment.js',
  durationSeconds: 0.013
}
{
  event: 'migrating',
  name: '20191020202716-create-user-subscription.js'
}
{
  event: 'migrated',
  name: '20191020202716-create-user-subscription.js',
  durationSeconds: 0.009
}
{ event: 'migrating', name: '20191022024102-create-image.js' }
{
  event: 'migrated',
  name: '20191022024102-create-image.js',
  durationSeconds: 0.011
}
{ event: 'migrating', name: '20191023020403-create-recipe-image.js' }
{
  event: 'migrated',
  name: '20191023020403-create-recipe-image.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20191026055147-remove-recipe-image-column.js'
}
{
  event: 'migrated',
  name: '20191026055147-remove-recipe-image-column.js',
  durationSeconds: 0.015
}
{
  event: 'migrating',
  name: '20200124223957-add-index-recipe_label-recipeId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223957-add-index-recipe_label-recipeId-fkey.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20200124223958-add-index-recipe_image-recipeId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223958-add-index-recipe_image-recipeId-fkey.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20200124223959-add-index-recipe-userId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223959-add-index-recipe-userId-fkey.js',
  durationSeconds: 0.009
}
{
  event: 'migrating',
  name: '20200203223959-add-unique-recipe_labels-recipeId.js'
}
{
  event: 'migrated',
  name: '20200203223959-add-unique-recipe_labels-recipeId.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20200203223960-add-unique-users-email.js'
}
{
  event: 'migrated',
  name: '20200203223960-add-unique-users-email.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20200206223979-add-index-recipe_labels-labelId-fkey.js'
}
{
  event: 'migrated',
  name: '20200206223979-add-index-recipe_labels-labelId-fkey.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20200206223980-add-unique-labels-userId-title.js'
}
Label titles updated: 0
Accounts with labels migrated: 0
{
  event: 'migrated',
  name: '20200206223980-add-unique-labels-userId-title.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20200206223981-labels-clean-title-commas.js'
}
{
  event: 'migrated',
  name: '20200206223981-labels-clean-title-commas.js',
  durationSeconds: 0.007
}
{ event: 'migrating', name: '20200823174732-create-friendship.js' }
{
  event: 'migrated',
  name: '20200823174732-create-friendship.js',
  durationSeconds: 0.008
}
{ event: 'migrating', name: '20200823175411-create-profile-item.js' }
{
  event: 'migrated',
  name: '20200823175411-create-profile-item.js',
  durationSeconds: 0.011
}
{ event: 'migrating', name: '20200907151614-add-user-handle.js' }
{
  event: 'migrated',
  name: '20200907151614-add-user-handle.js',
  durationSeconds: 0.005
}
{
  event: 'migrating',
  name: '20200907151615-add-unique-user-handle.js'
}
{
  event: 'migrated',
  name: '20200907151615-add-unique-user-handle.js',
  durationSeconds: 0.007
}
{ event: 'migrating', name: '20200907151616-add-user-social.js' }
{
  event: 'migrated',
  name: '20200907151616-add-user-social.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20200908020403-create-user-profile-image.js'
}
{
  event: 'migrated',
  name: '20200908020403-create-user-profile-image.js',
  durationSeconds: 0.008
}
{ event: 'migrating', name: '20200908020404-add-unique-friendship.js' }
{
  event: 'migrated',
  name: '20200908020404-add-unique-friendship.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20210207000000-add-notnull-shopping-list-item-title.js'
}
{
  event: 'migrated',
  name: '20210207000000-add-notnull-shopping-list-item-title.js',
  durationSeconds: 0.006
}
{
  event: 'migrating',
  name: '20221022000000-add-notnull-shopping-list-item-completed.js'
}
{
  event: 'migrated',
  name: '20221022000000-add-notnull-shopping-list-item-completed.js',
  durationSeconds: 0.005
}
{ event: 'migrating', name: '20221226000000-add-recipe-rating.js' }
{
  event: 'migrated',
  name: '20221226000000-add-recipe-rating.js',
  durationSeconds: 0.006
}
{ event: 'migrating', name: '20221226000000-image-userid-nullable.js' }
{
  event: 'migrated',
  name: '20221226000000-image-userid-nullable.js',
  durationSeconds: 0.006
}
{
  event: 'migrating',
  name: '20230716223979-add-index-mealplanitems-recipeId.js'
}
{
  event: 'migrated',
  name: '20230716223979-add-index-mealplanitems-recipeId.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20230716223989-add-index-mealplanitems-mealPlanId.js'
}
{
  event: 'migrated',
  name: '20230716223989-add-index-mealplanitems-mealPlanId.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20230716233979-add-index-shoppinglistitems-recipeId.js'
}
{
  event: 'migrated',
  name: '20230716233979-add-index-shoppinglistitems-recipeId.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20230716233989-add-index-shoppinglistitems-shoppingListId.js'
}
{
  event: 'migrated',
  name: '20230716233989-add-index-shoppinglistitems-shoppingListId.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20230716233997-add-index-recipe_images-imageId.js'
}
{
  event: 'migrated',
  name: '20230716233997-add-index-recipe_images-imageId.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20230716233998-add-index-images-userId.js'
}
{
  event: 'migrated',
  name: '20230716233998-add-index-images-userId.js',
  durationSeconds: 0.007
}
{
  event: 'migrating',
  name: '20230716233999-add-index-recipes-fromUserId.js'
}
{
  event: 'migrated',
  name: '20230716233999-add-index-recipes-fromUserId.js',
  durationSeconds: 0.009
}
{
  event: 'migrating',
  name: '20230717000000-add-index-messages-recipeId.js'
}
{
  event: 'migrated',
  name: '20230717000000-add-index-messages-recipeId.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20230717000001-add-index-messages-originalRecipeId.js'
}
{
  event: 'migrated',
  name: '20230717000001-add-index-messages-originalRecipeId.js',
  durationSeconds: 0.008
}
{
  event: 'migrating',
  name: '20231105000001-update-image-userId-fk.js'
}
{
  event: 'migrated',
  name: '20231105000001-update-image-userId-fk.js',
  durationSeconds: 0.007
}
All migrations performed successfully
Migrations complete.

This time it worked just fine

/content/images/2024/01/miscapps-21.png

Here I’ll whip up a terrible recipe:

I now have Interview Toast setup in the instance:

/content/images/2024/01/miscapps-23.png

Let’s consider what a Kubernetes deployment might look like.

This time, instead of Bing AI, we’ll use kompose

I’ll brew install the tool:

builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ brew install kompose
==> Downloading https://ghcr.io/v2/homebrew/portable-ruby/portable-ruby/blobs/sha256:f7be167f7ac4f296b9f4c5874ceeea4aafd9999c3c7f2b0378cae7dd273e2322
Running `brew update --auto-update`...
######################################################################## 100.0%
==> Pouring portable-ruby-3.1.4.x86_64_linux.bottle.tar.gz
==> Auto-updated Homebrew!
Updated 6 taps (codefresh-io/cli, derailed/popeye, derailed/k9s, f1bonacc1/tap, homebrew/core and homebrew/cask).
==> New Formulae
argc                  cidr                  dotter                ocaml@4               pdfrip                retire
cargo-sweep           dalfox                drogon                open-simh             purr                  solo2-cli
chisel-tunnel         direwolf              glbinding@2           opensca-cli           redress               yatas
==> New Casks
amie                             focusrite-control-2              navigraph-simlink                screens-assist
anka-build-cloud-controller      garmin-basecamp                  notes-better                     senabluetoothdevicemanager
anka-build-cloud-registry        hapigo                           nx-studio                        truhu
blockstream-green                heynote                          october                          vimcal
brightintosh                     imazing-profile-editor           reader                           wave
cardo-update                     keyboard-cowboy                  roam                             wiso-steuer-2024
creality-print                   lw-scanner                       sakura                           xact
egovframedev                     navigraph-charts                 salt

You have 52 outdated formulae installed.

==> Downloading https://ghcr.io/v2/homebrew/core/kompose/manifests/1.31.2
########################################################################################################################### 100.0%
==> Fetching dependencies for kompose: linux-headers@5.15 and binutils
==> Downloading https://ghcr.io/v2/homebrew/core/linux-headers/5.15/manifests/5.15.145
########################################################################################################################### 100.0%
==> Fetching linux-headers@5.15
==> Downloading https://ghcr.io/v2/homebrew/core/linux-headers/5.15/blobs/sha256:5ff9e9a7fb1c4b0cd7be57f78fb4b274819ee0eef7c28ace0
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/binutils/manifests/2.41_1
########################################################################################################################### 100.0%
==> Fetching binutils
==> Downloading https://ghcr.io/v2/homebrew/core/binutils/blobs/sha256:ff5d14a01dbfc4e423713561d02e704b90577eb32feb7ee276b1a4065eb
########################################################################################################################### 100.0%
==> Fetching kompose
==> Downloading https://ghcr.io/v2/homebrew/core/kompose/blobs/sha256:99550035b8938199c758e0097ffc8ca525a7af19d48080d50955153c6587
########################################################################################################################### 100.0%
==> Installing dependencies for kompose: linux-headers@5.15 and binutils
==> Installing kompose dependency: linux-headers@5.15
==> Downloading https://ghcr.io/v2/homebrew/core/linux-headers/5.15/manifests/5.15.145
Already downloaded: /home/builder/.cache/Homebrew/downloads/13d1e09899f505428b13e23a85789cc43c263c4c5ab626adf8c5ffa8ca8a7f90--linux-headers@5.15-5.15.145.bottle_manifest.json
==> Pouring linux-headers@5.15--5.15.145.x86_64_linux.bottle.tar.gz
🍺  /home/linuxbrew/.linuxbrew/Cellar/linux-headers@5.15/5.15.145: 961 files, 5.7MB
==> Installing kompose dependency: binutils
==> Downloading https://ghcr.io/v2/homebrew/core/binutils/manifests/2.41_1
Already downloaded: /home/builder/.cache/Homebrew/downloads/5719c1ab7c1a761142ecbf7e99d196b1a5e6c1cae43737424dae6237609d4b86--binutils-2.41_1.bottle_manifest.json
==> Pouring binutils--2.41_1.x86_64_linux.bottle.tar.gz
🍺  /home/linuxbrew/.linuxbrew/Cellar/binutils/2.41_1: 4,729 files, 469MB
==> Installing kompose
==> Pouring kompose--1.31.2.x86_64_linux.bottle.tar.gz
==> Caveats
Bash completion has been installed to:
  /home/linuxbrew/.linuxbrew/etc/bash_completion.d
==> Summary
🍺  /home/linuxbrew/.linuxbrew/Cellar/kompose/1.31.2: 9 files, 20MB
==> Running `brew cleanup kompose`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Warning: The following dependents of upgraded formulae are outdated but will not
be upgraded because they are not bottled:
  popeye
  process-compose
  punq
  kdash
==> Upgrading 31 dependents of upgraded formulae:
Disable this behaviour by setting HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
alsa-lib 1.2.9 -> 1.2.10, cairo 1.16.0_5 -> 1.18.0, devspace 6.3.2 -> 6.3.7, gawk 5.2.2 -> 5.3.0, gettext 0.21.1 -> 0.22.4, gh 2.32.0 -> 2.40.1, glib 2.78.1 -> 2.78.3, gnutls 3.8.0 -> 3.8.2, harfbuzz 7.3.0 -> 8.3.0, hugo 0.112.3 -> 0.121.1, icu4c 72.1 -> 73.2, jpeg-turbo 2.1.5.1 -> 3.0.0, jq 1.6 -> 1.7.1, kubernetes-cli 1.27.3 -> 1.29.0, libnghttp2 1.53.0 -> 1.58.0, libtiff 4.5.0 -> 4.6.0, libxml2 2.11.4 -> 2.12.3, libxrandr 1.5.3 -> 1.5.4, nettle 3.9 -> 3.9.1, oniguruma 6.9.8 -> 6.9.9, opa 0.53.0 -> 0.60.0, openjdk@11 11.0.19 -> 11.0.21, p11-kit 0.24.1_1 -> 0.25.3, python-yq 3.2.2 -> 3.2.3, python@3.10 3.10.11 -> 3.10.13_1, pyyaml 6.0_1 -> 6.0.1_1, sonar-scanner 4.8.0.2856 -> 5.0.1.3006, unbound 1.17.1 -> 1.19.0, util-linux 2.39.2 -> 2.39.3, vault 1.13.2 -> 1.14.4, yq 4.31.1 -> 4.40.5
==> Downloading https://ghcr.io/v2/homebrew/core/util-linux/manifests/2.39.3
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/glib/manifests/2.78.3
########################################################################################################################### 100.0%
==> Fetching dependencies for glib: util-linux and openssl@3
==> Downloading https://ghcr.io/v2/homebrew/core/util-linux/manifests/2.39.3
Already downloaded: /home/builder/.cache/Homebrew/downloads/41a532ac610c3f3e608ce0c0b34e0cc55ae8d08082fdd609a0568d497ca7dc68--util-linux-2.39.3.bottle_manifest.json
==> Fetching util-linux
==> Downloading https://ghcr.io/v2/homebrew/core/util-linux/blobs/sha256:caccd0657b3b8e6555e63b0b924f76ce211807c18109c491408aa9edb
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/openssl/3/manifests/3.2.0_1
########################################################################################################################### 100.0%
==> Fetching openssl@3
==> Downloading https://ghcr.io/v2/homebrew/core/openssl/3/blobs/sha256:7412ee0230d222844ed3ea22cd356c6e99b4d3c0528800b08e05f51b5e
########################################################################################################################### 100.0%
==> Fetching glib
==> Downloading https://ghcr.io/v2/homebrew/core/glib/blobs/sha256:8657e856d1ea8610a23fccaf0cfa94b3a3271986c54101b69d5734ad1f9b080
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/cairo/manifests/1.18.0
Already downloaded: /home/builder/.cache/Homebrew/downloads/1b8b7f19e38cb0e029497dfe0ad3f3557f605025c5575bdfe86c17b889c6e6d1--cairo-1.18.0.bottle_manifest.json
==> Downloading https://ghcr.io/v2/homebrew/core/icu4c/manifests/73.2
Already downloaded: /home/builder/.cache/Homebrew/downloads/2e5082de52a2c85ae665e51f8d0de0651611397cb02f4b4e2bb37898ba52a629--icu4c-73.2.bottle_manifest.json
==> Fetching icu4c
==> Downloading https://ghcr.io/v2/homebrew/core/icu4c/blobs/sha256:714dc987ca9c9b594e6e17c02640f93b49074f81264be16051a93a8f5f674d
Already downloaded: /home/builder/.cache/Homebrew/downloads/d467742181d935c7e6128244f70d5fced229aaf7d1f2b7af9ae1009f663c0f49--icu4c--73.2.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/harfbuzz/manifests/8.3.0
Already downloaded: /home/builder/.cache/Homebrew/downloads/8fccd91a473694ef9d916ce061ae69cb18243bdab491757fae06fed911dd5b63--harfbuzz-8.3.0.bottle_manifest.json
==> Fetching dependencies for harfbuzz: ca-certificates and cairo
==> Downloading https://ghcr.io/v2/homebrew/core/ca-certificates/manifests/2023-12-12
########################################################################################################################### 100.0%
==> Fetching ca-certificates
==> Downloading https://ghcr.io/v2/homebrew/core/ca-certificates/blobs/sha256:5c99ffd0861f01adc19cab495027024f7d890e42a9e7b689706b
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/cairo/manifests/1.18.0
Already downloaded: /home/builder/.cache/Homebrew/downloads/1b8b7f19e38cb0e029497dfe0ad3f3557f605025c5575bdfe86c17b889c6e6d1--cairo-1.18.0.bottle_manifest.json
==> Fetching cairo
==> Downloading https://ghcr.io/v2/homebrew/core/cairo/blobs/sha256:35cd3a9f81432449e9b0f457a20c8e94e0d2c804da0210a5deffe9e0b8549b
Already downloaded: /home/builder/.cache/Homebrew/downloads/e9accdc16bccef2e5abcb467ff30111310b3f425ef7ba3b32ca56650658a7835--cairo--1.18.0.x86_64_linux.bottle.tar.gz
==> Fetching harfbuzz
==> Downloading https://ghcr.io/v2/homebrew/core/harfbuzz/blobs/sha256:14b087a8f369a66f82888d7cd0d10eafdf3624d292bd1374be89225fc74
Already downloaded: /home/builder/.cache/Homebrew/downloads/febbb52e1ec3b9da06469a27a07714576997bbe95432fb9e3f2b3a69d30ed459--harfbuzz--8.3.0.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/jpeg-turbo/manifests/3.0.0
Already downloaded: /home/builder/.cache/Homebrew/downloads/325e745bcb7840d6a83df7bfcafaa4dbc30b29140f252fb82b7714493df29d6d--jpeg-turbo-3.0.0.bottle_manifest.json
==> Fetching jpeg-turbo
==> Downloading https://ghcr.io/v2/homebrew/core/jpeg-turbo/blobs/sha256:44943f311f5999c84a5f6d3695b0fa96f6f336eb0f50a5a0174df7feb
Already downloaded: /home/builder/.cache/Homebrew/downloads/07522ed0e693cbb4aad5c3962ddaba9db85e6d2855246055d1a957ea6175bc13--jpeg-turbo--3.0.0.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/libtiff/manifests/4.6.0
Already downloaded: /home/builder/.cache/Homebrew/downloads/9c8455a2f6bcea7bf3e3461218fba309141b0b0553b6e1afc5fe64e63caccff3--libtiff-4.6.0.bottle_manifest.json
==> Fetching libtiff
==> Downloading https://ghcr.io/v2/homebrew/core/libtiff/blobs/sha256:9a6e0bb56c39b72a33b0a5629dc3fd49e4f1391513bcf7d04a764523cc03
Already downloaded: /home/builder/.cache/Homebrew/downloads/f2e6d4dae98e74b1a502cfeb93bb624150576d3024ee19ca7687bb1e18e46958--libtiff--4.6.0.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/alsa-lib/manifests/1.2.10
Already downloaded: /home/builder/.cache/Homebrew/downloads/f8e0e4a32aebd4f2125c90c3739136bcf9dbde8c807c8cc515a4558850648c5b--alsa-lib-1.2.10.bottle_manifest.json
==> Fetching alsa-lib
==> Downloading https://ghcr.io/v2/homebrew/core/alsa-lib/blobs/sha256:deec067e633df0a59c1428b8ced127ede44334ce834bb975823fd969bf1
Already downloaded: /home/builder/.cache/Homebrew/downloads/352ac261bab0810167ac73f2933ea888a5829c744d33ae709789ae202451da65--alsa-lib--1.2.10.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/libxrandr/manifests/1.5.4
Already downloaded: /home/builder/.cache/Homebrew/downloads/1fbb247f2abdbccf5d8f0682109acc180855bab14fd80473fbc15f74ebef0dbc--libxrandr-1.5.4.bottle_manifest.json
==> Downloading https://ghcr.io/v2/homebrew/core/openjdk/11/manifests/11.0.21
Already downloaded: /home/builder/.cache/Homebrew/downloads/cc2a280e3d623e1cc5bdcbb047604a548ad30174ad35007de84eb1179ab22665--openjdk@11-11.0.21.bottle_manifest.json
==> Fetching dependencies for openjdk@11: libxrandr and cups
==> Downloading https://ghcr.io/v2/homebrew/core/libxrandr/manifests/1.5.4
Already downloaded: /home/builder/.cache/Homebrew/downloads/1fbb247f2abdbccf5d8f0682109acc180855bab14fd80473fbc15f74ebef0dbc--libxrandr-1.5.4.bottle_manifest.json
==> Fetching libxrandr
==> Downloading https://ghcr.io/v2/homebrew/core/libxrandr/blobs/sha256:c48b622aed3d5e99e225601dca2d129fda08585571d948f8737f3e6a4b
Already downloaded: /home/builder/.cache/Homebrew/downloads/9578c92b41ef412d5c904f8f11092ba784a2a4e54da628b3fc41a5670c697f14--libxrandr--1.5.4.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/cups/manifests/2.4.7
Already downloaded: /home/builder/.cache/Homebrew/downloads/1ebb7c2856d92dc0b096195218281b4cbd6eac0f85459b88ee2cedf1c548ffca--cups-2.4.7.bottle_manifest.json
==> Fetching cups
==> Downloading https://ghcr.io/v2/homebrew/core/cups/blobs/sha256:1e35d934788dd9b07ca70d43210a934b17b894a9b4b1d54bc06224c3fa416f1
Already downloaded: /home/builder/.cache/Homebrew/downloads/988a8b88284a9ff7fbbe580c6da4cda1b13cdd98bf68c7df882e17cf513c18fa--cups--2.4.7.x86_64_linux.bottle.tar.gz
==> Fetching openjdk@11
==> Downloading https://ghcr.io/v2/homebrew/core/openjdk/11/blobs/sha256:1c0235a9aa57f27553c0838c430f12b26cc672c8f8da950cfb5a2bbe3
Already downloaded: /home/builder/.cache/Homebrew/downloads/f34ce846cc220743fce7e94b152401cf4b0c99bce27c3047921410508bb30445--openjdk@11--11.0.21.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/kubernetes-cli/manifests/1.29.0
########################################################################################################################### 100.0%
==> Fetching kubernetes-cli
==> Downloading https://ghcr.io/v2/homebrew/core/kubernetes-cli/blobs/sha256:0fba10c62bc80131fa4eef13a380d870a6355c37eeaae9be40b3e
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/devspace/manifests/6.3.7
########################################################################################################################### 100.0%
==> Fetching devspace
==> Downloading https://ghcr.io/v2/homebrew/core/devspace/blobs/sha256:20e347ff25fead961c1fa582e98a656f0b89ef88a811d6035e6913c8847
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/libxml2/manifests/2.12.3
########################################################################################################################### 100.0%
==> Fetching libxml2
==> Downloading https://ghcr.io/v2/homebrew/core/libxml2/blobs/sha256:be89416eea709ce3efe90ccdd8d5810d2906a625d86186e3308656121243
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/gettext/manifests/0.22.4
########################################################################################################################### 100.0%
==> Fetching gettext
==> Downloading https://ghcr.io/v2/homebrew/core/gettext/blobs/sha256:dc631c067b99d2620dfa6994b89bd6435ab95d4f1e5f19e00eca290df8c3
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/gawk/manifests/5.3.0
Already downloaded: /home/builder/.cache/Homebrew/downloads/a9bb151f02093d3c934a4d4052e99b2255bb438b67350221b0955fa554543528--gawk-5.3.0.bottle_manifest.json
==> Fetching gawk
==> Downloading https://ghcr.io/v2/homebrew/core/gawk/blobs/sha256:3027c2f8c9192fa35085eeadb6c88b1aa9f577660a7955b0a30f7ca2121c34a
Already downloaded: /home/builder/.cache/Homebrew/downloads/3caaf74ea790ba4457c6e3eeba09dea3fccef6e9fe3bd08363b4aac32d4d8b3a--gawk--5.3.0.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/gh/manifests/2.40.1
########################################################################################################################### 100.0%
==> Fetching gh
==> Downloading https://ghcr.io/v2/homebrew/core/gh/blobs/sha256:aeaf38c711233f19b1d2229ce2b67c2ab217a4df60665005b7bf7682ede79bde
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/nettle/manifests/3.9.1
Already downloaded: /home/builder/.cache/Homebrew/downloads/a02ae3a1ecc6cff56e632f6452fd57f3fa72be16cde0b69e5b47747283fd3f6b--nettle-3.9.1.bottle_manifest.json
==> Downloading https://ghcr.io/v2/homebrew/core/p11-kit/manifests/0.25.3
Already downloaded: /home/builder/.cache/Homebrew/downloads/cc547bf2f72da03680090015f5b720aff280ce33de9c33783a69c24fe97a4246--p11-kit-0.25.3.bottle_manifest.json
==> Downloading https://ghcr.io/v2/homebrew/core/libnghttp2/manifests/1.58.0
Already downloaded: /home/builder/.cache/Homebrew/downloads/9b2081d73959d4308c84740344d5f8169bbb9125dd76d20a71556a11a0171253--libnghttp2-1.58.0.bottle_manifest.json
==> Fetching libnghttp2
==> Downloading https://ghcr.io/v2/homebrew/core/libnghttp2/blobs/sha256:1723ddcaacc3ad10052a0cb4e8cc1644f1ddfd6b51a70a3f9a57c98b2
Already downloaded: /home/builder/.cache/Homebrew/downloads/4e42c9e99cc2728437e8fb2755d30d563af5d2608e9d5a21daa1efe25ff59a46--libnghttp2--1.58.0.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/unbound/manifests/1.19.0
Already downloaded: /home/builder/.cache/Homebrew/downloads/5ef4c435591de61234a87b911b8f2793a67aea99fa163a6c156bb9303f7fac11--unbound-1.19.0.bottle_manifest.json
==> Fetching dependencies for unbound: libevent
==> Downloading https://ghcr.io/v2/homebrew/core/libevent/manifests/2.1.12_1
Already downloaded: /home/builder/.cache/Homebrew/downloads/68b113f9ab63db45f4e1860de522ce2ca4fa081eb3c0d5c7d6005a35c3cf8d06--libevent-2.1.12_1.bottle_manifest.json
==> Fetching libevent
==> Downloading https://ghcr.io/v2/homebrew/core/libevent/blobs/sha256:83ef4ce689a91f6fca013d6b4b0b2fcda3706080f8e0cccd056a3d94d6b
Already downloaded: /home/builder/.cache/Homebrew/downloads/694835ad258e8dc6e9758f34fd0f18013d7a61f2a6ac1ca0e481f652d80b086a--libevent--2.1.12_1.x86_64_linux.bottle.tar.gz
==> Fetching unbound
==> Downloading https://ghcr.io/v2/homebrew/core/unbound/blobs/sha256:441cecb4591f00aa52dce8eda2d1b98400f622f73ad6cc64a6c202cd7496
Already downloaded: /home/builder/.cache/Homebrew/downloads/ab177d46ddb8eff68f54a3bea4e69bd4fc1a60b67ed0653c72099f2af95ccae7--unbound--1.19.0.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/gnutls/manifests/3.8.2
Already downloaded: /home/builder/.cache/Homebrew/downloads/451d8f1012df2b218c76390367beb96684e0899d24cb219dfe87eb047945fc14--gnutls-3.8.2.bottle_manifest.json
==> Fetching dependencies for gnutls: nettle and p11-kit
==> Downloading https://ghcr.io/v2/homebrew/core/nettle/manifests/3.9.1
Already downloaded: /home/builder/.cache/Homebrew/downloads/a02ae3a1ecc6cff56e632f6452fd57f3fa72be16cde0b69e5b47747283fd3f6b--nettle-3.9.1.bottle_manifest.json
==> Fetching nettle
==> Downloading https://ghcr.io/v2/homebrew/core/nettle/blobs/sha256:89cb9f1c702bee6f67ef0a3d4c075b6d6b0dddf54853120a33272ce704d5a
Already downloaded: /home/builder/.cache/Homebrew/downloads/4f6411a11bae436aba59c5ddd1fbcc2e5cfcead5ad961d48825b0a6874a810b4--nettle--3.9.1.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/p11-kit/manifests/0.25.3
Already downloaded: /home/builder/.cache/Homebrew/downloads/cc547bf2f72da03680090015f5b720aff280ce33de9c33783a69c24fe97a4246--p11-kit-0.25.3.bottle_manifest.json
==> Fetching p11-kit
==> Downloading https://ghcr.io/v2/homebrew/core/p11-kit/blobs/sha256:4f7ca2105451e0561951b327254cb179505798e8b5c491e9e4ee9124b085
Already downloaded: /home/builder/.cache/Homebrew/downloads/e441dd030a4c5e47b033444568349edf886d873d198246f5059ceb6a0d8da3be--p11-kit--0.25.3.x86_64_linux.bottle.tar.gz
==> Fetching gnutls
==> Downloading https://ghcr.io/v2/homebrew/core/gnutls/blobs/sha256:3827cd32aa6a9cd29a91bf788afa51ac5a88bf87f93355fd0f260a17480ab
Already downloaded: /home/builder/.cache/Homebrew/downloads/ab2b007ff74d0b969784c00add47757d2935c8d00c43a7ad5b353a7072fe59ea--gnutls--3.8.2.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/hugo/manifests/0.121.1
########################################################################################################################### 100.0%
==> Fetching hugo
==> Downloading https://ghcr.io/v2/homebrew/core/hugo/blobs/sha256:5805f549466d9b647a9741eb615efe774f1c5b6bc4d27eb7f7f8a8b3a37f3c5
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/oniguruma/manifests/6.9.9
Already downloaded: /home/builder/.cache/Homebrew/downloads/35140c4d3995b75388bed026ef6d0acbb4d6076047cdcd895bfd996c0c8d6487--oniguruma-6.9.9.bottle_manifest.json
==> Fetching oniguruma
==> Downloading https://ghcr.io/v2/homebrew/core/oniguruma/blobs/sha256:18f0e52bb660582698f390023d5db08208ed468d2f01758018b9ee8c69
Already downloaded: /home/builder/.cache/Homebrew/downloads/18b2f3d05c68f5556cb5915841c3466dd74309703d87d4170663ea9f340d4b07--oniguruma--6.9.9.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/jq/manifests/1.7.1
########################################################################################################################### 100.0%
==> Fetching jq
==> Downloading https://ghcr.io/v2/homebrew/core/jq/blobs/sha256:ed490b627b327b3458a70a78c546be07d57bfc6958921f875b76e85f6be51f47
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/opa/manifests/0.60.0
########################################################################################################################### 100.0%
==> Fetching opa
==> Downloading https://ghcr.io/v2/homebrew/core/opa/blobs/sha256:89595ae6fd5a8f399eb4dd00cffe438fe44073ddefb954f2811e437dcb5b281c
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/pyyaml/manifests/6.0.1_1
Already downloaded: /home/builder/.cache/Homebrew/downloads/bb167293fae0bea9e7a2acd7496bdc04de8fd1d22a0d4c554a42477cda37397a--pyyaml-6.0.1_1.bottle_manifest.json
==> Downloading https://ghcr.io/v2/homebrew/core/python-yq/manifests/3.2.3-3
Already downloaded: /home/builder/.cache/Homebrew/downloads/4e35262202df99b72af260cb317f87c97b3045a9dfa19b92fddd4a23e83834ba--python-yq-3.2.3-3.bottle_manifest.json
==> Fetching dependencies for python-yq: python-argcomplete, python@3.12 and pyyaml
==> Downloading https://ghcr.io/v2/homebrew/core/python-argcomplete/manifests/3.2.1-1
########################################################################################################################### 100.0%
==> Fetching python-argcomplete
==> Downloading https://ghcr.io/v2/homebrew/core/python-argcomplete/blobs/sha256:82d477507c01c441680446607ca80954bfd0e007cf72b338c
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/python/3.12/manifests/3.12.1
########################################################################################################################### 100.0%
==> Fetching python@3.12
==> Downloading https://ghcr.io/v2/homebrew/core/python/3.12/blobs/sha256:719f8a93546953005ad6212199a9c2b0a2bed37fa3c5334752ecdc2d
########################################################################################################################### 100.0%
==> Downloading https://ghcr.io/v2/homebrew/core/pyyaml/manifests/6.0.1_1
Already downloaded: /home/builder/.cache/Homebrew/downloads/bb167293fae0bea9e7a2acd7496bdc04de8fd1d22a0d4c554a42477cda37397a--pyyaml-6.0.1_1.bottle_manifest.json
==> Fetching pyyaml
==> Downloading https://ghcr.io/v2/homebrew/core/pyyaml/blobs/sha256:d53f5c35de430f055c83fdbb1e857b3423cbea9c66523f929cb525876ef55
Already downloaded: /home/builder/.cache/Homebrew/downloads/5b88422fd57d9450a7b896afa2e43e9be19e73adc040b076787472a14b05a0c5--pyyaml--6.0.1_1.x86_64_linux.bottle.tar.gz
==> Fetching python-yq
==> Downloading https://ghcr.io/v2/homebrew/core/python-yq/blobs/sha256:63f678c0102d10126d0fd16e95ae98039bfc6ff4088d53ccb64b6b9c37
Already downloaded: /home/builder/.cache/Homebrew/downloads/fbde7202abd607c7311a116e3c5eb75119134ecc4128aec93b24a42630ca1284--python-yq--3.2.3.x86_64_linux.bottle.3.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/python/3.10/manifests/3.10.13_1
Already downloaded: /home/builder/.cache/Homebrew/downloads/a610d721ced33d58a439f710e86510660530bd18118bfcc145eb76722ab06a79--python@3.10-3.10.13_1.bottle_manifest.json
==> Downloading https://ghcr.io/v2/homebrew/core/sonar-scanner/manifests/5.0.1.3006
Already downloaded: /home/builder/.cache/Homebrew/downloads/bde285dde2b469ed3741edde1c6e63c6f5c0d73a261ae95a3f781a3f60be7de2--sonar-scanner-5.0.1.3006.bottle_manifest.json
==> Fetching dependencies for sonar-scanner: openjdk
==> Downloading https://ghcr.io/v2/homebrew/core/openjdk/manifests/21.0.1
Already downloaded: /home/builder/.cache/Homebrew/downloads/bb8a78296beb0d39c64166622e2df1a553bdf78572bac0ce95765a7007a46b8a--openjdk-21.0.1.bottle_manifest.json
==> Fetching openjdk
==> Downloading https://ghcr.io/v2/homebrew/core/openjdk/blobs/sha256:90f191bb2d7a118a3509df17eddcad6ec3be26a13064b10145a26c7de157
Already downloaded: /home/builder/.cache/Homebrew/downloads/4e29e5190abcabaab52998e57d5a98eca856c4086bd95381f010220d49194405--openjdk--21.0.1.x86_64_linux.bottle.tar.gz
==> Fetching sonar-scanner
==> Downloading https://ghcr.io/v2/homebrew/core/sonar-scanner/blobs/sha256:13fadfccf23d8bffe46a1dfac508917900a967c558ba1e59701099
Already downloaded: /home/builder/.cache/Homebrew/downloads/dfeafc91e3fc600c69e85ca68cbc1dc088cf2bbf42c252d49f38575f233dd094--sonar-scanner--5.0.1.3006.all.bottle.tar.gz
Warning: vault has been deprecated because it will change its license to BUSL on the next release!
==> Downloading https://ghcr.io/v2/homebrew/core/vault/manifests/1.14.4
Already downloaded: /home/builder/.cache/Homebrew/downloads/84fb3014e50109659e36e00593d156a6dbf649880ce6a18a51be615c2991b405--vault-1.14.4.bottle_manifest.json
==> Fetching vault
==> Downloading https://ghcr.io/v2/homebrew/core/vault/blobs/sha256:f4518c13f4613bb7adf425c5eabcd4a4cff94fdfe7def03201ac193f10fa7c
Already downloaded: /home/builder/.cache/Homebrew/downloads/e81f25965d2b286041e8123c9d0d654baf23f0b51d360ad73e75006df6c16369--vault--1.14.4.x86_64_linux.bottle.tar.gz
==> Downloading https://ghcr.io/v2/homebrew/core/yq/manifests/4.40.5
########################################################################################################################### 100.0%
==> Fetching yq
==> Downloading https://ghcr.io/v2/homebrew/core/yq/blobs/sha256:c5f992d8e25b2c444253d6e84ab255612c8f724bc1c28f6fdc608263afb23baf
########################################################################################################################### 100.0%
==> Upgrading glib
  2.78.1 -> 2.78.3

==> Installing dependencies for glib: util-linux and openssl@3
==> Installing glib dependency: util-linux
==> Downloading https://ghcr.io/v2/homebrew/core/util-linux/manifests/2.39.3
Already downloaded: /home/builder/.cache/Homebrew/downloads/41a532ac610c3f3e608ce0c0b34e0cc55ae8d08082fdd609a0568d497ca7dc68--util-linux-2.39.3.bottle_manifest.json
==> Pouring util-linux--2.39.3.x86_64_linux.bottle.tar.gz
Error: Too many open files @ rb_sysopen - /home/linuxbrew/.linuxbrew/Cellar/util-linux/2.39.3/sbin/swapon

# just verifying since I noticed that fileopen error
$ brew install kompose
Warning: kompose 1.31.2 is already installed and up-to-date.
To reinstall 1.31.2, run:
  brew reinstall kompose

Then run to convert

builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ ls
LICENSE.md  README.md  activate.sh  docker-compose.yml  migrate.sh  proxy.conf
builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ kompose convert
WARN Restart policy 'unless-stopped' in service typesense is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service pushpin is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service postgres is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service browserless is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service ingredient-instruction-classifier is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service proxy is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service static is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service api is not supported, convert it to 'always'
WARN Service "api" won't be created because 'ports' is not specified
WARN Service "browserless" won't be created because 'ports' is not specified
WARN Service "ingredient-instruction-classifier" won't be created because 'ports' is not specified
WARN Service "postgres" won't be created because 'ports' is not specified
WARN Volume mount on the host "/home/builder/Workspaces/RecipeSage-selfhost/proxy.conf" isn't supported - ignoring path on the host
WARN Service "pushpin" won't be created because 'ports' is not specified
WARN Service "static" won't be created because 'ports' is not specified
WARN Service "typesense" won't be created because 'ports' is not specified
INFO Kubernetes file "proxy-service.yaml" created
INFO Kubernetes file "api-deployment.yaml" created
INFO Kubernetes file "apimedia-persistentvolumeclaim.yaml" created
INFO Kubernetes file "browserless-deployment.yaml" created
INFO Kubernetes file "ingredient-instruction-classifier-deployment.yaml" created
INFO Kubernetes file "postgres-deployment.yaml" created
INFO Kubernetes file "postgresdata-persistentvolumeclaim.yaml" created
INFO Kubernetes file "proxy-deployment.yaml" created
INFO Kubernetes file "proxy-claim0-persistentvolumeclaim.yaml" created
INFO Kubernetes file "pushpin-deployment.yaml" created
INFO Kubernetes file "static-deployment.yaml" created
INFO Kubernetes file "typesense-deployment.yaml" created
INFO Kubernetes file "typesensedata-persistentvolumeclaim.yaml" created

I can see Kompose really wants the ports explictly called out. We can determine them just by reading the compose YAML or looking at what Docker itself used

/content/images/2024/01/miscapps-24.png

I added them to the compose yaml:

version: '3.7'
services:
  proxy: # The proxy must not be removed. If needed, point your own proxy to this container, rather than removing this
    container_name: recipesage_proxy
    image: nginx
    volumes:
    - ./proxy.conf:/etc/nginx/conf.d/default.conf
    ports:
    - 80:80
    depends_on:
      - static
      - api
      - pushpin
    restart: unless-stopped
  static: # Hosts frontend assets
    container_name: recipesage_static
    image: julianpoy/recipesage-selfhost:static-v2.12.10
    restart: unless-stopped
    ports:
    - 80:80
  api: # Hosts backend API
    container_name: recipesage_api
    image: julianpoy/recipesage-selfhost:api-v2.12.10
    depends_on:
      - postgres
      - typesense
      - pushpin
      - browserless
    ports:
    - 3000:3000
    command: npx ts-node --swc --project packages/backend/tsconfig.json packages/backend/src/bin/www
    environment:
      - STORAGE_TYPE=filesystem
      - FILESYSTEM_STORAGE_PATH=/rs-media
      - NODE_ENV=selfhost
      - VERBOSE=false
      - VERSION=selfhost
      - POSTGRES_DB=recipesage_selfhost
      - POSTGRES_USER=recipesage_selfhost
      - POSTGRES_PASSWORD=recipesage_selfhost
      - POSTGRES_PORT=5432
      - POSTGRES_HOST=postgres
      - POSTGRES_SSL=false
      - POSTGRES_LOGGING=false
      - DATABASE_URL=postgresql://recipesage_selfhost:recipesage_selfhost@postgres:5432/recipesage_selfhost
      - GCM_KEYPAIR
      - SENTRY_DSN
      - GRIP_URL=http://pushpin:5561/
      - GRIP_KEY=changeme
      - SEARCH_PROVIDER=typesense
      - 'TYPESENSE_NODES=[{"host": "typesense", "port": 8108, "protocol": "http"}]'
      - TYPESENSE_API_KEY=recipesage_selfhost
      - STRIPE_SK
      - STRIPE_WEBHOOK_SECRET
      - BROWSERLESS_HOST=browserless
      - BROWSERLESS_PORT=3000
      - INGREDIENT_INSTRUCTION_CLASSIFIER_URL=http://ingredient-instruction-classifier:3000/
    volumes:
      - apimedia:/rs-media
    restart: unless-stopped
  typesense:
    container_name: recipesage_typesense
    image: typesense/typesense:0.24.1
    ports:
    - 8108:8108
    volumes:
      - typesensedata:/data
    command: "--data-dir /data --api-key=recipesage_selfhost --enable-cors"
    restart: unless-stopped
  pushpin: # Provides websocket support
    container_name: recipesage_pushpin
    image: julianpoy/pushpin:2023-09-17
    entrypoint: /bin/sh -c
    command:
      [
        'sed -i "s/sig_key=changeme/sig_key=$$GRIP_KEY/" /etc/pushpin/pushpin.conf && echo "* $${TARGET},over_http" > /etc/pushpin/routes && pushpin --merge-output',
      ]
    environment:
      - GRIP_KEY=changeme
      - TARGET=api:3000
    restart: unless-stopped
    ports:
    - 7999:7999
  postgres: # Database
    container_name: recipesage_postgres
    image: postgres:16
    ports:
    - 5432:5432
    environment:
      - POSTGRES_DB=recipesage_selfhost
      - POSTGRES_USER=recipesage_selfhost
      - POSTGRES_PASSWORD=recipesage_selfhost
    volumes:
      - postgresdata:/var/lib/postgresql/data
    restart: unless-stopped
  browserless: # A headless browser for scraping websites with the auto import tool
    container_name: recipesage_browserless
    image: browserless/chrome:1.57-puppeteer-19.2.2
    ports:
    - 3000:3000
    environment:
      - MAX_CONCURRENT_SESSIONS=3
      - MAX_QUEUE_LENGTH=10
    restart: unless-stopped
  ingredient-instruction-classifier: # A mini server that runs a machine learning model able to classify text for improved automatic import
    container_name: recipesage_classifier
    image: julianpoy/ingredient-instruction-classifier:1.4.9
    environment:
      - SENTENCE_EMBEDDING_BATCH_SIZE=200
      - PREDICTION_CONCURRENCY=4
    restart: unless-stopped
    ports:
    - 3000:3000
volumes:
  apimedia:
    driver: local
  typesensedata:
    driver: local
  postgresdata:
    driver: local

and tried again

$ kompose convert
WARN Restart policy 'unless-stopped' in service ingredient-instruction-classifier is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service proxy is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service static is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service api is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service typesense is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service pushpin is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service postgres is not supported, convert it to 'always'
WARN Restart policy 'unless-stopped' in service browserless is not supported, convert it to 'always'
WARN Volume mount on the host "/home/builder/Workspaces/RecipeSage-selfhost/proxy.conf" isn't supported - ignoring path on the host
INFO Kubernetes file "api-service.yaml" created
INFO Kubernetes file "browserless-service.yaml" created
INFO Kubernetes file "ingredient-instruction-classifier-service.yaml" created
INFO Kubernetes file "postgres-service.yaml" created
INFO Kubernetes file "proxy-service.yaml" created
INFO Kubernetes file "pushpin-service.yaml" created
INFO Kubernetes file "static-service.yaml" created
INFO Kubernetes file "typesense-service.yaml" created
INFO Kubernetes file "api-deployment.yaml" created
INFO Kubernetes file "apimedia-persistentvolumeclaim.yaml" created
INFO Kubernetes file "browserless-deployment.yaml" created
INFO Kubernetes file "ingredient-instruction-classifier-deployment.yaml" created
INFO Kubernetes file "postgres-deployment.yaml" created
INFO Kubernetes file "postgresdata-persistentvolumeclaim.yaml" created
INFO Kubernetes file "proxy-deployment.yaml" created
INFO Kubernetes file "proxy-claim0-persistentvolumeclaim.yaml" created
INFO Kubernetes file "pushpin-deployment.yaml" created
INFO Kubernetes file "static-deployment.yaml" created
INFO Kubernetes file "typesense-deployment.yaml" created
INFO Kubernetes file "typesensedata-persistentvolumeclaim.yaml" created

The conf we’ll need to sort by hand:

$ kubectl create cm proxycfg --from-file=./proxy.conf --dry-run -o yaml
W1225 09:26:37.830146   14272 helpers.go:692] --dry-run is deprecated and can be replaced with --dry-run=client.
apiVersion: v1
data:
  proxy.conf: |
    server {
      client_max_body_size 1G;

      listen 80 default_server;

      server_name localhost;

      location /grip/ws {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_connect_timeout 1h;
        proxy_send_timeout 1h;
        proxy_read_timeout 1h;
        proxy_pass http://pushpin:7999/ws;
      }

      # Alias minio in case minio container is not present
      set $minio http://minio:9000;
      location /minio/ {
        rewrite ^/minio/(.*)$ /$1 break;
        proxy_pass $minio;
      }

      location /api/ {
        proxy_pass http://api:3000/;
      }

      location / {
        proxy_pass http://static:80/;
      }
    }
kind: ConfigMap
metadata:
  creationTimestamp: null
  name: proxycfg

I’ll move all the current k8s files into a folder

builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ mkdir k8s
builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ mv *.yaml ./k8s

add that cm

$ kubectl create cm proxycfg --from-file=./proxy.conf --dry-run -o yaml > ./k8s/proxycm.yaml
W1225 09:27:11.517589   14743 helpers.go:692] --dry-run is deprecated and can be replaced with --dry-run=client.

I commented out the PVC for proxy and updated the deploy to use the CM

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.31.2 (HEAD)
  creationTimestamp: null
  labels:
    io.kompose.service: proxy
  name: proxy
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: proxy
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.31.2 (HEAD)
      creationTimestamp: null
      labels:
        io.kompose.network/recipesage-selfhost-default: "true"
        io.kompose.service: proxy
    spec:
      containers:
        - image: nginx
          name: recipesage-proxy
          ports:
            - containerPort: 80
              hostPort: 80
              protocol: TCP
          resources: {}
          volumeMounts:
            - name: config
              mountPath: /etc/nginx/conf.d/default.conf
              subPath: proxy.conf
      restartPolicy: Always
      volumes:
        - name: config
          configMap:
            name: proxycfg
status: {}

This is a pretty large payload

/content/images/2024/01/miscapps-25.png

So I’ll test on my test cluster

builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ kubectx mac81
Switched to context "mac81".
builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ kubectl get nodes
NAME                  STATUS   ROLES                  AGE   VERSION
anna-macbookair       Ready    control-plane,master   12d   v1.27.6+k3s1
isaac-macbookpro      Ready    <none>                 12d   v1.27.6+k3s1
builder-macbookpro2   Ready    <none>                 12d   v1.27.6+k3s1
builder@DESKTOP-QADGF36:~/Workspaces/RecipeSage-selfhost$ kubectl apply -f ./k8s/
deployment.apps/api created
service/api created
persistentvolumeclaim/apimedia created
deployment.apps/browserless created
service/browserless created
deployment.apps/ingredient-instruction-classifier created
service/ingredient-instruction-classifier created
deployment.apps/postgres created
service/postgres created
persistentvolumeclaim/postgresdata created
deployment.apps/proxy created
service/proxy created
configmap/proxycfg created
deployment.apps/pushpin created
service/pushpin created
deployment.apps/static created
service/static created
deployment.apps/typesense created
service/typesense created
persistentvolumeclaim/typesensedata created

I watched as it brought pods up

Every 2.0s: kubectl get pods                                                                                                     DESKTOP-QADGF36: Mon Dec 25 09:35:18 2023

NAME                                                 READY   STATUS              RESTARTS   AGE
my-shell                                             1/1     Running             0          10d
browserless-b4c854-2qp4c                             0/1     ContainerCreating   0          87s
ingredient-instruction-classifier-5b8bd9c99d-thqxw   0/1     ContainerCreating   0          87s
proxy-7fcc4d6748-g57x7                               0/1     Pending             0          87s
pushpin-7f557dcbf4-v5w5p                             0/1     ContainerCreating   0          87s
static-8f77dcd89-trz47                               0/1     Pending             0          87s
typesense-5785d4c6b8-4xdf7                           0/1     ContainerCreating   0          87s
postgres-788d8cc76-bbpzx                             1/1     Running             0          87s
api-767dd95f67-c9jff                                 1/1     Running             0          87s

The API crashed, but then I realized there are a lot of interdependencies here. It crashed just because the pushpin wasnt up

$ kubectl logs api-767dd95f67-c9jff
Request #1703518539940: Request to Node 0 failed due to "ECONNREFUSED connect ECONNREFUSED 10.43.21.217:8108"
Request #1703518539940: Sleeping for 0.1s and then retrying request...
Request #1703518539940: Request to Node 0 failed due to "ECONNREFUSED connect ECONNREFUSED 10.43.21.217:8108"
Request #1703518539940: Sleeping for 0.1s and then retrying request...
Request #1703518539940: Request to Node 0 failed due to "ECONNREFUSED connect ECONNREFUSED 10.43.21.217:8108"
Request #1703518539940: Sleeping for 0.1s and then retrying request...
Database connection established
Request #1703518539940: Request to Node 0 failed due to "ECONNREFUSED connect ECONNREFUSED 10.43.21.217:8108"
Request #1703518539940: Sleeping for 0.1s and then retrying request...
Request #1703518539940: Request to Node 0 failed due to "ECONNREFUSED connect ECONNREFUSED 10.43.21.217:8108"
Request #1703518539940: Sleeping for 0.1s and then retrying request...
Request #1703518539940: Request to Node 0 failed due to "ECONNREFUSED connect ECONNREFUSED 10.43.21.217:8108"
Request #1703518539940: Sleeping for 0.1s and then retrying request...
Error: connect ECONNREFUSED 10.43.21.217:8108
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16) {
  errno: -111,

Let’s check again

$ kubectl get pods
NAME                                                 READY   STATUS    RESTARTS      AGE
my-shell                                             1/1     Running   0             10d
proxy-7fcc4d6748-g57x7                               0/1     Pending   0             3m57s
static-8f77dcd89-trz47                               0/1     Pending   0             3m57s
postgres-788d8cc76-bbpzx                             1/1     Running   0             3m57s
pushpin-7f557dcbf4-v5w5p                             1/1     Running   0             3m57s
typesense-5785d4c6b8-4xdf7                           1/1     Running   0             3m57s
ingredient-instruction-classifier-5b8bd9c99d-thqxw   1/1     Running   0             3m57s
api-767dd95f67-c9jff                                 1/1     Running   4 (92s ago)   3m57s
browserless-b4c854-2qp4c                             1/1     Running   0             3m57s

Seems we have some port challenges

Events:
  Type     Reason            Age                    From               Message
  ----     ------            ----                   ----               -------
  Warning  FailedScheduling  5m2s                   default-scheduler  0/3 nodes are available: 3 node(s) didn't have free ports for the requested pod ports. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod..
  Warning  FailedScheduling  4m56s (x2 over 4m59s)  default-scheduler  0/3 nodes are available: 3 node(s) didn't have free ports for the requested pod ports. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod..

I’m going to change the “hostport” settings in proxy and static to 8820, and 8810 then change the services to use them in “targetPort”. The actual destinations on the containers stay port 80 and the service handles port 80 so the in-between doesn’t really matter.

Now we are running

$ kubectl get pods
NAME                                                 READY   STATUS    RESTARTS        AGE
my-shell                                             1/1     Running   0               10d
postgres-788d8cc76-bbpzx                             1/1     Running   0               9m10s
pushpin-7f557dcbf4-v5w5p                             1/1     Running   0               9m10s
typesense-5785d4c6b8-4xdf7                           1/1     Running   0               9m10s
ingredient-instruction-classifier-5b8bd9c99d-thqxw   1/1     Running   0               9m10s
api-767dd95f67-c9jff                                 1/1     Running   4 (6m45s ago)   9m10s
browserless-b4c854-2qp4c                             1/1     Running   0               9m10s
proxy-78ddd6cd4b-wqc27                               1/1     Running   0               61s
static-58d56f54b4-zdqxn                              1/1     Running   0               61s

I need to translate the “migrate.sh” to a kubernetes command:

$ cat ./migrate.sh
#!/bin/sh

echo "Performing migrations. This could take up to 1 minute."

docker compose exec api tsx packages/backend/src/migrate

echo "Migrations complete."

Which is pretty straightforward:

$ kubectl exec api-767dd95f67-c9jff tsx "packages/backend/src/migrate"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
{ event: 'migrating', name: '20180903174506-create-user.js' }
{
  event: 'migrated',
  name: '20180903174506-create-user.js',
  durationSeconds: 0.036
}
{ event: 'migrating', name: '20180903225610-create-session.js' }
{
  event: 'migrated',
  name: '20180903225610-create-session.js',
  durationSeconds: 0.031
}
{ event: 'migrating', name: '20180905031747-create-recipe.js' }
{
  event: 'migrated',
  name: '20180905031747-create-recipe.js',
  durationSeconds: 0.025
}
{ event: 'migrating', name: '20180905031929-create-message.js' }
{
  event: 'migrated',
  name: '20180905031929-create-message.js',
  durationSeconds: 0.025
}
{ event: 'migrating', name: '20180905040040-create-meal-plan.js' }
{
  event: 'migrated',
  name: '20180905040040-create-meal-plan.js',
  durationSeconds: 0.021
}
{ event: 'migrating', name: '20180905040042-create-meal-plan-item.js' }
{
  event: 'migrated',
  name: '20180905040042-create-meal-plan-item.js',
  durationSeconds: 0.036
}
{ event: 'migrating', name: '20180905040055-create-shopping-list.js' }
{
  event: 'migrated',
  name: '20180905040055-create-shopping-list.js',
  durationSeconds: 0.038
}
{
  event: 'migrating',
  name: '20180905040211-create-shopping-list-item.js'
}
{
  event: 'migrated',
  name: '20180905040211-create-shopping-list-item.js',
  durationSeconds: 0.037
}
{ event: 'migrating', name: '20180905042925-create-label.js' }
{
  event: 'migrated',
  name: '20180905042925-create-label.js',
  durationSeconds: 0.024
}
{ event: 'migrating', name: '20180905042940-create-recipe_label.js' }
{
  event: 'migrated',
  name: '20180905042940-create-recipe_label.js',
  durationSeconds: 0.035
}
{
  event: 'migrating',
  name: '20180909144458-create-meal-plan_collaborator.js'
}
{
  event: 'migrated',
  name: '20180909144458-create-meal-plan_collaborator.js',
  durationSeconds: 0.033
}
{
  event: 'migrating',
  name: '20180909150203-create-shopping-list_collaborator.js'
}
{
  event: 'migrated',
  name: '20180909150203-create-shopping-list_collaborator.js',
  durationSeconds: 0.023
}
{ event: 'migrating', name: '20180909194501-create-fcm-token.js' }
{
  event: 'migrated',
  name: '20180909194501-create-fcm-token.js',
  durationSeconds: 0.032
}
{ event: 'migrating', name: '20190318151614-add-recipe-indexedAt.js' }
{
  event: 'migrated',
  name: '20190318151614-add-recipe-indexedAt.js',
  durationSeconds: 0.013
}
{
  event: 'migrating',
  name: '20191020162730-add-user-stripeCustomerId.js'
}
{
  event: 'migrated',
  name: '20191020162730-add-user-stripeCustomerId.js',
  durationSeconds: 0.019
}
{ event: 'migrating', name: '20191020164936-create-stripe-payment.js' }
{
  event: 'migrated',
  name: '20191020164936-create-stripe-payment.js',
  durationSeconds: 0.042
}
{
  event: 'migrating',
  name: '20191020202716-create-user-subscription.js'
}
{
  event: 'migrated',
  name: '20191020202716-create-user-subscription.js',
  durationSeconds: 0.031
}
{ event: 'migrating', name: '20191022024102-create-image.js' }
{
  event: 'migrated',
  name: '20191022024102-create-image.js',
  durationSeconds: 0.029
}
{ event: 'migrating', name: '20191023020403-create-recipe-image.js' }
{
  event: 'migrated',
  name: '20191023020403-create-recipe-image.js',
  durationSeconds: 0.036
}
{
  event: 'migrating',
  name: '20191026055147-remove-recipe-image-column.js'
}
{
  event: 'migrated',
  name: '20191026055147-remove-recipe-image-column.js',
  durationSeconds: 0.054
}
{
  event: 'migrating',
  name: '20200124223957-add-index-recipe_label-recipeId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223957-add-index-recipe_label-recipeId-fkey.js',
  durationSeconds: 0.031
}
{
  event: 'migrating',
  name: '20200124223958-add-index-recipe_image-recipeId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223958-add-index-recipe_image-recipeId-fkey.js',
  durationSeconds: 0.024
}
{
  event: 'migrating',
  name: '20200124223959-add-index-recipe-userId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223959-add-index-recipe-userId-fkey.js',
  durationSeconds: 0.039
}
{
  event: 'migrating',
  name: '20200203223959-add-unique-recipe_labels-recipeId.js'
}
{
  event: 'migrated',
  name: '20200203223959-add-unique-recipe_labels-recipeId.js',
  durationSeconds: 0.043
}
{
  event: 'migrating',
  name: '20200203223960-add-unique-users-email.js'
}
{
  event: 'migrated',
  name: '20200203223960-add-unique-users-email.js',
  durationSeconds: 0.028
}
{
  event: 'migrating',
  name: '20200206223979-add-index-recipe_labels-labelId-fkey.js'
}
{
  event: 'migrated',
  name: '20200206223979-add-index-recipe_labels-labelId-fkey.js',
  durationSeconds: 0.033
}
{
  event: 'migrating',
  name: '20200206223980-add-unique-labels-userId-title.js'
}
Label titles updated: 0
Accounts with labels migrated: 0
{
  event: 'migrated',
  name: '20200206223980-add-unique-labels-userId-title.js',
  durationSeconds: 0.044
}
{
  event: 'migrating',
  name: '20200206223981-labels-clean-title-commas.js'
}
{
  event: 'migrated',
  name: '20200206223981-labels-clean-title-commas.js',
  durationSeconds: 0.026
}
{ event: 'migrating', name: '20200823174732-create-friendship.js' }
{
  event: 'migrated',
  name: '20200823174732-create-friendship.js',
  durationSeconds: 0.036
}
{ event: 'migrating', name: '20200823175411-create-profile-item.js' }
{
  event: 'migrated',
  name: '20200823175411-create-profile-item.js',
  durationSeconds: 0.037
}
{ event: 'migrating', name: '20200907151614-add-user-handle.js' }
{
  event: 'migrated',
  name: '20200907151614-add-user-handle.js',
  durationSeconds: 0.022
}
{
  event: 'migrating',
  name: '20200907151615-add-unique-user-handle.js'
}
{
  event: 'migrated',
  name: '20200907151615-add-unique-user-handle.js',
  durationSeconds: 0.026
}
{ event: 'migrating', name: '20200907151616-add-user-social.js' }
{
  event: 'migrated',
  name: '20200907151616-add-user-social.js',
  durationSeconds: 0.029
}
{
  event: 'migrating',
  name: '20200908020403-create-user-profile-image.js'
}
{
  event: 'migrated',
  name: '20200908020403-create-user-profile-image.js',
  durationSeconds: 0.034
}
{ event: 'migrating', name: '20200908020404-add-unique-friendship.js' }
{
  event: 'migrated',
  name: '20200908020404-add-unique-friendship.js',
  durationSeconds: 0.027
}
{
  event: 'migrating',
  name: '20210207000000-add-notnull-shopping-list-item-title.js'
}
{
  event: 'migrated',
  name: '20210207000000-add-notnull-shopping-list-item-title.js',
  durationSeconds: 0.032
}
{
  event: 'migrating',
  name: '20221022000000-add-notnull-shopping-list-item-completed.js'
}
{
  event: 'migrated',
  name: '20221022000000-add-notnull-shopping-list-item-completed.js',
  durationSeconds: 0.023
}
{ event: 'migrating', name: '20221226000000-add-recipe-rating.js' }
{
  event: 'migrated',
  name: '20221226000000-add-recipe-rating.js',
  durationSeconds: 0.019
}
{ event: 'migrating', name: '20221226000000-image-userid-nullable.js' }
{
  event: 'migrated',
  name: '20221226000000-image-userid-nullable.js',
  durationSeconds: 0.016
}
{
  event: 'migrating',
  name: '20230716223979-add-index-mealplanitems-recipeId.js'
}
{
  event: 'migrated',
  name: '20230716223979-add-index-mealplanitems-recipeId.js',
  durationSeconds: 0.023
}
{
  event: 'migrating',
  name: '20230716223989-add-index-mealplanitems-mealPlanId.js'
}
{
  event: 'migrated',
  name: '20230716223989-add-index-mealplanitems-mealPlanId.js',
  durationSeconds: 0.019
}
{
  event: 'migrating',
  name: '20230716233979-add-index-shoppinglistitems-recipeId.js'
}
{
  event: 'migrated',
  name: '20230716233979-add-index-shoppinglistitems-recipeId.js',
  durationSeconds: 0.023
}
{
  event: 'migrating',
  name: '20230716233989-add-index-shoppinglistitems-shoppingListId.js'
}
{
  event: 'migrated',
  name: '20230716233989-add-index-shoppinglistitems-shoppingListId.js',
  durationSeconds: 0.022
}
{
  event: 'migrating',
  name: '20230716233997-add-index-recipe_images-imageId.js'
}
{
  event: 'migrated',
  name: '20230716233997-add-index-recipe_images-imageId.js',
  durationSeconds: 0.021
}
{
  event: 'migrating',
  name: '20230716233998-add-index-images-userId.js'
}
{
  event: 'migrated',
  name: '20230716233998-add-index-images-userId.js',
  durationSeconds: 0.028
}
{
  event: 'migrating',
  name: '20230716233999-add-index-recipes-fromUserId.js'
}
{
  event: 'migrated',
  name: '20230716233999-add-index-recipes-fromUserId.js',
  durationSeconds: 0.026
}
{
  event: 'migrating',
  name: '20230717000000-add-index-messages-recipeId.js'
}
{
  event: 'migrated',
  name: '20230717000000-add-index-messages-recipeId.js',
  durationSeconds: 0.023
}
{
  event: 'migrating',
  name: '20230717000001-add-index-messages-originalRecipeId.js'
}
{
  event: 'migrated',
  name: '20230717000001-add-index-messages-originalRecipeId.js',
  durationSeconds: 0.013
}
{
  event: 'migrating',
  name: '20231105000001-update-image-userId-fk.js'
}
{
  event: 'migrated',
  name: '20231105000001-update-image-userId-fk.js',
  durationSeconds: 0.013
}
All migrations performed successfully

It’s not looking too good

/content/images/2024/01/miscapps-26.png

Though after a moment, I realized “static” is really the front door service:

/content/images/2024/01/miscapps-27.png

However, I got the same error as before:

/content/images/2024/01/miscapps-28.png

The error comes from the fact that the “static” pod really wants to talk on the same port over to the proxy

127.0.0.1 - - [25/Dec/2023:15:51:19 +0000] "POST /api/users/register?false=false& HTTP/1.1" 404 555 "http://localhost:8888/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "-"
127.0.0.1 - - [25/Dec/2023:15:51:24 +0000] "POST /api/users/register?false=false& HTTP/1.1" 404 555 "http://localhost:8888/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" "-"
2023/12/25 15:51:24 [error] 31#31: *9 open() "/usr/share/nginx/html/api/users/register" failed (2: No such file or directory), client: 127.0.0.1, server: localhost, request: "POST /api/users/register?false=false& HTTP/1.1", host: "localhost:8888", referrer: "http://localhost:8888/"

It took me a bit to figure out how i managed to swap up targetport with hostport instead of containerport. All in, there was a lot of trial and error.

I push an update to a fork of the repo. You can view all the YAML I ended up using here.

Now the question is hosting with an external ingress.

I’ll switch to my primary cluster and create a namespace for this app

builder@DESKTOP-QADGF36:~/Workspaces/idjohnson-recipesage-selfhost$ kubectx mac77
Switched to context "mac77".
builder@DESKTOP-QADGF36:~/Workspaces/idjohnson-recipesage-selfhost$ kubectl create ns recipesage
namespace/recipesage created

I’ll then create the workloads there

$ kubectl apply -n recipesage -f ./kubernetes/
deployment.apps/api created
service/api created
persistentvolumeclaim/apimedia created
deployment.apps/browserless created
service/browserless created
deployment.apps/ingredient-instruction-classifier created
service/ingredient-instruction-classifier created
deployment.apps/postgres created
service/postgres created
persistentvolumeclaim/postgresdata created
deployment.apps/proxy created
service/proxy created
configmap/proxycfg created
deployment.apps/pushpin created
service/pushpin created
deployment.apps/static created
service/static created
deployment.apps/typesense created
service/typesense created
persistentvolumeclaim/typesensedata created

I’ll create the A Record

$ cat r53-recipesage.json
{
    "Comment": "CREATE recipes fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "recipes.freshbrewed.science",
          "Type": "A",
          "TTL": 300,
          "ResourceRecords": [
            {
              "Value": "75.73.224.240"
            }
          ]
        }
      }
    ]
  }
$ aws route53 change-resource-record-sets --hosted-zone-id Z39E8QFU0F9PZP --change-batch file://r53-recipesage.json
{
    "ChangeInfo": {
        "Id": "/change/C03664723NPE8NG6TKYIC",
        "Status": "PENDING",
        "SubmittedAt": "2023-12-25T18:49:04.565Z",
        "Comment": "CREATE recipes fb.s A record "
    }
}

Then an ingress

$ cat ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    ingress.kubernetes.io/proxy-body-size: "0"
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/client-max-body-size: "0"
    nginx.org/proxy-connect-timeout: "3600"
    nginx.org/proxy-read-timeout: "3600"
  creationTimestamp: "2023-11-21T00:30:38Z"
  generation: 1
  labels:
    app.kubernetes.io/name: recipesage
  name: recipesingress
  namespace: recipesage
spec:
  rules:
  - host: recipes.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: proxy
            port:
              number: 8820
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - recipes.freshbrewed.science
    secretName: recipes-tls

$ kubectl apply -f ingress.yaml
ingress.networking.k8s.io/recipesingress created    

I was a bit worried about the impact to the cluster, so I checked Datadog:

Pod utilization went up a bit

/content/images/2024/01/miscapps-29.png

I also saw a spike in availability of pods as they started to launch

/content/images/2024/01/miscapps-30.png

We can’t forget to do the data migrate.

Here is a one-liner you can use as well (though change namespace if appropriate)

$ kubectl exec -n recipesage `kubectl get pods -l io.kompose.service=api -n recipesage -o jsonpath='{.items[*].metadata.name}'` tsx "packages/backend/src/migrate"

Which applied

$ kubectl exec -n recipesage `kubectl get pods -l io.kompose.service=api -n recipesage -o jsonpath='{.items[*].metadata.name}'` tsx "packages/backend/src/migrate"
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
{ event: 'migrating', name: '20180903174506-create-user.js' }
{
  event: 'migrated',
  name: '20180903174506-create-user.js',
  durationSeconds: 0.045
}
{ event: 'migrating', name: '20180903225610-create-session.js' }
{
  event: 'migrated',
  name: '20180903225610-create-session.js',
  durationSeconds: 0.023
}
{ event: 'migrating', name: '20180905031747-create-recipe.js' }
{
  event: 'migrated',
  name: '20180905031747-create-recipe.js',
  durationSeconds: 0.017
}
{ event: 'migrating', name: '20180905031929-create-message.js' }
{
  event: 'migrated',
  name: '20180905031929-create-message.js',
  durationSeconds: 0.026
}
{ event: 'migrating', name: '20180905040040-create-meal-plan.js' }
{
  event: 'migrated',
  name: '20180905040040-create-meal-plan.js',
  durationSeconds: 0.028
}
{ event: 'migrating', name: '20180905040042-create-meal-plan-item.js' }
{
  event: 'migrated',
  name: '20180905040042-create-meal-plan-item.js',
  durationSeconds: 0.017
}
{ event: 'migrating', name: '20180905040055-create-shopping-list.js' }
{
  event: 'migrated',
  name: '20180905040055-create-shopping-list.js',
  durationSeconds: 0.026
}
{
  event: 'migrating',
  name: '20180905040211-create-shopping-list-item.js'
}
{
  event: 'migrated',
  name: '20180905040211-create-shopping-list-item.js',
  durationSeconds: 0.029
}
{ event: 'migrating', name: '20180905042925-create-label.js' }
{
  event: 'migrated',
  name: '20180905042925-create-label.js',
  durationSeconds: 0.023
}
{ event: 'migrating', name: '20180905042940-create-recipe_label.js' }
{
  event: 'migrated',
  name: '20180905042940-create-recipe_label.js',
  durationSeconds: 0.019
}
{
  event: 'migrating',
  name: '20180909144458-create-meal-plan_collaborator.js'
}
{
  event: 'migrated',
  name: '20180909144458-create-meal-plan_collaborator.js',
  durationSeconds: 0.018
}
{
  event: 'migrating',
  name: '20180909150203-create-shopping-list_collaborator.js'
}
{
  event: 'migrated',
  name: '20180909150203-create-shopping-list_collaborator.js',
  durationSeconds: 0.025
}
{ event: 'migrating', name: '20180909194501-create-fcm-token.js' }
{
  event: 'migrated',
  name: '20180909194501-create-fcm-token.js',
  durationSeconds: 0.021
}
{ event: 'migrating', name: '20190318151614-add-recipe-indexedAt.js' }
{
  event: 'migrated',
  name: '20190318151614-add-recipe-indexedAt.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20191020162730-add-user-stripeCustomerId.js'
}
{
  event: 'migrated',
  name: '20191020162730-add-user-stripeCustomerId.js',
  durationSeconds: 0.013
}
{ event: 'migrating', name: '20191020164936-create-stripe-payment.js' }
{
  event: 'migrated',
  name: '20191020164936-create-stripe-payment.js',
  durationSeconds: 0.026
}
{
  event: 'migrating',
  name: '20191020202716-create-user-subscription.js'
}
{
  event: 'migrated',
  name: '20191020202716-create-user-subscription.js',
  durationSeconds: 0.021
}
{ event: 'migrating', name: '20191022024102-create-image.js' }
{
  event: 'migrated',
  name: '20191022024102-create-image.js',
  durationSeconds: 0.025
}
{ event: 'migrating', name: '20191023020403-create-recipe-image.js' }
{
  event: 'migrated',
  name: '20191023020403-create-recipe-image.js',
  durationSeconds: 0.013
}
{
  event: 'migrating',
  name: '20191026055147-remove-recipe-image-column.js'
}
{
  event: 'migrated',
  name: '20191026055147-remove-recipe-image-column.js',
  durationSeconds: 0.02
}
{
  event: 'migrating',
  name: '20200124223957-add-index-recipe_label-recipeId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223957-add-index-recipe_label-recipeId-fkey.js',
  durationSeconds: 0.012
}
{
  event: 'migrating',
  name: '20200124223958-add-index-recipe_image-recipeId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223958-add-index-recipe_image-recipeId-fkey.js',
  durationSeconds: 0.015
}
{
  event: 'migrating',
  name: '20200124223959-add-index-recipe-userId-fkey.js'
}
{
  event: 'migrated',
  name: '20200124223959-add-index-recipe-userId-fkey.js',
  durationSeconds: 0.019
}
{
  event: 'migrating',
  name: '20200203223959-add-unique-recipe_labels-recipeId.js'
}
{
  event: 'migrated',
  name: '20200203223959-add-unique-recipe_labels-recipeId.js',
  durationSeconds: 0.023
}
{
  event: 'migrating',
  name: '20200203223960-add-unique-users-email.js'
}
{
  event: 'migrated',
  name: '20200203223960-add-unique-users-email.js',
  durationSeconds: 0.018
}
{
  event: 'migrating',
  name: '20200206223979-add-index-recipe_labels-labelId-fkey.js'
}
{
  event: 'migrated',
  name: '20200206223979-add-index-recipe_labels-labelId-fkey.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20200206223980-add-unique-labels-userId-title.js'
}
Label titles updated: 0
Accounts with labels migrated: 0
{
  event: 'migrated',
  name: '20200206223980-add-unique-labels-userId-title.js',
  durationSeconds: 0.015
}
{
  event: 'migrating',
  name: '20200206223981-labels-clean-title-commas.js'
}
{
  event: 'migrated',
  name: '20200206223981-labels-clean-title-commas.js',
  durationSeconds: 0.01
}
{ event: 'migrating', name: '20200823174732-create-friendship.js' }
{
  event: 'migrated',
  name: '20200823174732-create-friendship.js',
  durationSeconds: 0.018
}
{ event: 'migrating', name: '20200823175411-create-profile-item.js' }
{
  event: 'migrated',
  name: '20200823175411-create-profile-item.js',
  durationSeconds: 0.058
}
{ event: 'migrating', name: '20200907151614-add-user-handle.js' }
{
  event: 'migrated',
  name: '20200907151614-add-user-handle.js',
  durationSeconds: 0.02
}
{
  event: 'migrating',
  name: '20200907151615-add-unique-user-handle.js'
}
{
  event: 'migrated',
  name: '20200907151615-add-unique-user-handle.js',
  durationSeconds: 0.031
}
{ event: 'migrating', name: '20200907151616-add-user-social.js' }
{
  event: 'migrated',
  name: '20200907151616-add-user-social.js',
  durationSeconds: 0.023
}
{
  event: 'migrating',
  name: '20200908020403-create-user-profile-image.js'
}
{
  event: 'migrated',
  name: '20200908020403-create-user-profile-image.js',
  durationSeconds: 0.026
}
{ event: 'migrating', name: '20200908020404-add-unique-friendship.js' }
{
  event: 'migrated',
  name: '20200908020404-add-unique-friendship.js',
  durationSeconds: 0.021
}
{
  event: 'migrating',
  name: '20210207000000-add-notnull-shopping-list-item-title.js'
}
{
  event: 'migrated',
  name: '20210207000000-add-notnull-shopping-list-item-title.js',
  durationSeconds: 0.013
}
{
  event: 'migrating',
  name: '20221022000000-add-notnull-shopping-list-item-completed.js'
}
{
  event: 'migrated',
  name: '20221022000000-add-notnull-shopping-list-item-completed.js',
  durationSeconds: 0.01
}
{ event: 'migrating', name: '20221226000000-add-recipe-rating.js' }
{
  event: 'migrated',
  name: '20221226000000-add-recipe-rating.js',
  durationSeconds: 0.009
}
{ event: 'migrating', name: '20221226000000-image-userid-nullable.js' }
{
  event: 'migrated',
  name: '20221226000000-image-userid-nullable.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20230716223979-add-index-mealplanitems-recipeId.js'
}
{
  event: 'migrated',
  name: '20230716223979-add-index-mealplanitems-recipeId.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20230716223989-add-index-mealplanitems-mealPlanId.js'
}
{
  event: 'migrated',
  name: '20230716223989-add-index-mealplanitems-mealPlanId.js',
  durationSeconds: 0.009
}
{
  event: 'migrating',
  name: '20230716233979-add-index-shoppinglistitems-recipeId.js'
}
{
  event: 'migrated',
  name: '20230716233979-add-index-shoppinglistitems-recipeId.js',
  durationSeconds: 0.02
}
{
  event: 'migrating',
  name: '20230716233989-add-index-shoppinglistitems-shoppingListId.js'
}
{
  event: 'migrated',
  name: '20230716233989-add-index-shoppinglistitems-shoppingListId.js',
  durationSeconds: 0.022
}
{
  event: 'migrating',
  name: '20230716233997-add-index-recipe_images-imageId.js'
}
{
  event: 'migrated',
  name: '20230716233997-add-index-recipe_images-imageId.js',
  durationSeconds: 0.017
}
{
  event: 'migrating',
  name: '20230716233998-add-index-images-userId.js'
}
{
  event: 'migrated',
  name: '20230716233998-add-index-images-userId.js',
  durationSeconds: 0.012
}
{
  event: 'migrating',
  name: '20230716233999-add-index-recipes-fromUserId.js'
}
{
  event: 'migrated',
  name: '20230716233999-add-index-recipes-fromUserId.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20230717000000-add-index-messages-recipeId.js'
}
{
  event: 'migrated',
  name: '20230717000000-add-index-messages-recipeId.js',
  durationSeconds: 0.01
}
{
  event: 'migrating',
  name: '20230717000001-add-index-messages-originalRecipeId.js'
}
{
  event: 'migrated',
  name: '20230717000001-add-index-messages-originalRecipeId.js',
  durationSeconds: 0.014
}
{
  event: 'migrating',
  name: '20231105000001-update-image-userId-fk.js'
}
{
  event: 'migrated',
  name: '20231105000001-update-image-userId-fk.js',
  durationSeconds: 0.015
}
All migrations performed successfully

I now have a fully functional RecipeSage site at https://recipes.freshbrewed.science/

Summary

Hopefully you found something useful, be it Docku for monitoring Docker storage, Noisedash, a simple and easy to use white noise generator or RecipeSage for collecting and sharing recipes.

You should be able to use my instances presently at:

Though, of course the point is to show people how to host themselves.

I will, for now, leave RecipeSage, but it did jump the pod count a bit in the production cluster:

/content/images/2024/01/miscapps-31.png

Kubernetes Docker Opensource RecipeSage Doku NoiseDash

Have something to add? Feedback? Try our new forums

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes