OS Apps: Movary and Moodist

Published: Apr 11, 2024 by Isaac Johnson

Today I wanted to checkout a couple Open-Source app’s that have been on my list for a while.

Movary is a very small clean app for sharing movie reviews and watchlists. It can pull data from TMDB easily. While it’s documented to run in Docker, we’ll set it up in Kubernetes.

Moodist is a very small but functional background sound generator similar to Noisedash. We’ll launch that in Kubernetes as well.

Movary

As mentioned, Movary is a movie tracking app for sharing the movies one has seen.

The standard install leverages docker.

Let’s see if we can convert that Docker Compose over to a Kubernetes Manifest

version: "3.5"

services:
  movary:
    image: leepeuker/movary:latest
    container_name: movary
    ports:
      - "80:80"
    environment:
      TMDB_API_KEY: "<tmdb_key>"
      DATABASE_MODE: "mysql"
      DATABASE_MYSQL_HOST: "mysql"
      DATABASE_MYSQL_NAME: "movary"
      DATABASE_MYSQL_USER: "movary_user"
      DATABASE_MYSQL_PASSWORD: "movary_password"
    volumes:
      - movary-storage:/app/storage

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_DATABASE: "movary"
      MYSQL_USER: "movary_user"
      MYSQL_PASSWORD: "movary_password"
      MYSQL_ROOT_PASSWORD: "<mysql_root_password>"
    volumes:
      - movary-db:/var/lib/mysql

volumes:
  movary-db:
  movary-storage:

The volumes should turn into PVCs and the rest should be deployments

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: movary-db-pvc
spec:
  storageClassName: local-path
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: movary-storage-pvc
spec:
  storageClassName: local-path
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: movary-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: movary
  template:
    metadata:
      labels:
        app: movary
    spec:
      containers:
        - name: movary
          image: leepeuker/movary:latest
          ports:
            - containerPort: 80
          env:
            - name: TMDB_API_KEY
              value: "171b839b39e603744dafc276a33b70e"
            - name: DATABASE_MODE
              value: "mysql"
            - name: DATABASE_MYSQL_HOST
              value: "movary-mysql"
            - name: DATABASE_MYSQL_NAME
              value: "movary"
            - name: DATABASE_MYSQL_USER
              value: "movary_user"
            - name: DATABASE_MYSQL_PASSWORD
              value: "movary_password"
          volumeMounts:
            - name: movary-storage
              mountPath: /app/storage
      volumes:
        - name: movary-storage
          persistentVolumeClaim:
            claimName: movary-storage-pvc

---

apiVersion: v1
kind: Service
metadata:
  name: movary-service
spec:
  selector:
    app: movary
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: movary-mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: movary-mysql
  template:
    metadata:
      labels:
        app: movary-mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL_DATABASE
              value: "movary"
            - name: MYSQL_USER
              value: "movary_user"
            - name: MYSQL_PASSWORD
              value: "movary_password"
            - name: MYSQL_ROOT_PASSWORD
              value: "movary_password"
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: mysql-storage
              mountPath: /var/lib/mysql
      volumes:
        - name: mysql-storage
          persistentVolumeClaim:
            claimName: movary-db-pvc

      
---

apiVersion: v1
kind: Service
metadata:
  name: movary-mysql
spec:
  selector:
    app: movary-mysql
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306
  type: ClusterIP        

You can get an API key for TMDB from here.

I’ll then create a namespace and apply the manifest

$ kubectl create ns movary
namespace/movary created

$ kubectl apply -f manifest.yaml -n movary
persistentvolumeclaim/movary-db-pvc created
persistentvolumeclaim/movary-storage-pvc created
deployment.apps/movary-deployment created
service/movary-service created
deployment.apps/movary-mysql created
service/movary-mysql created

It seemed to start right away

$ kubectl get svc -n movary
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
movary-service   ClusterIP   10.43.106.44   <none>        80/TCP     4s
movary-mysql     ClusterIP   10.43.49.104   <none>        3306/TCP   4s

$ kubectl get pods -n movary
NAME                                 READY   STATUS    RESTARTS   AGE
movary-deployment-59bc965c6c-6rtqb   1/1     Running   0          39s
movary-mysql-745cbd96b4-n97jv        1/1     Running   0          39s

A quick port-forward didn’t work too hot

$ kubectl port-forward svc/movary-service -n movary 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80
Handling connection for 8888
Handling connection for 8888
Handling connection for 8888
Handling connection for 8888
Handling connection for 8888
Handling connection for 8888
Handling connection for 8888
E0403 07:47:32.102960   23618 portforward.go:381] error copying from remote stream to local connection: readfrom tcp6 [::1]:8888->[::1]:46910: write tcp6 [::1]:8888->[::1]:46910: write: broken pipe
E0403 07:47:32.109495   23618 portforward.go:381] error copying from remote stream to local connection: readfrom tcp6 [::1]:8888->[::1]:46886: write tcp6 [::1]:8888->[::1]:46886: write: broken pipe

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

I can see plenty of errors in the logs

$ kubectl logs movary-deployment-59bc965c6c-hjzvc -n movary | tail -n10
127.0.0.1 - - [03/Apr/2024:12:47:42 +0000] "GET /serviceWorker.js HTTP/1.1" 304 0 "http://localhost:8888/serviceWorker.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
[2024-04-03T14:47:44.130009+02:00] movary.EMERGENCY: An exception occurred while executing a query: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'movary.user' doesn't exist {"exception":"[object] (Doctrine\\DBAL\\Exception\\TableNotFoundException(code: 1146): An exception occurred while executing a query: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'movary.user' doesn't exist at /app/vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php:49)\n[previous exception] [object] (Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 1146): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'movary.user' doesn't exist at /app/vendor/doctrine/dbal/src/Driver/PDO/Exception.php:28)\n[previous exception] [object] (PDOException(code: 42S02): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'movary.user' doesn't exist at /app/vendor/doctrine/dbal/src/Driver/PDO/Connection.php:71)"} []
[php-fpm:access] 127.0.0.1 -  03/Apr/2024:12:47:44 +0000 "GET /index.php" 500 /app/public/index.php 14.818 2048 67.49%
127.0.0.1 - - [03/Apr/2024:12:47:44 +0000] "GET / HTTP/1.1" 500 3394 "http://localhost:8888/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
[2024-04-03T14:47:45.225421+02:00] movary.EMERGENCY: An exception occurred while executing a query: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'movary.user' doesn't exist {"exception":"[object] (Doctrine\\DBAL\\Exception\\TableNotFoundException(code: 1146): An exception occurred while executing a query: SQLSTATE[42S02]: Base table or view not found: 1146 Table 'movary.user' doesn't exist at /app/vendor/doctrine/dbal/src/Driver/API/MySQL/ExceptionConverter.php:49)\n[previous exception] [object] (Doctrine\\DBAL\\Driver\\PDO\\Exception(code: 1146): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'movary.user' doesn't exist at /app/vendor/doctrine/dbal/src/Driver/PDO/Exception.php:28)\n[previous exception] [object] (PDOException(code: 42S02): SQLSTATE[42S02]: Base table or view not found: 1146 Table 'movary.user' doesn't exist at /app/vendor/doctrine/dbal/src/Driver/PDO/Connection.php:71)"} []
[php-fpm:access] 127.0.0.1 -  03/Apr/2024:12:47:45 +0000 "GET /index.php" 500 /app/public/index.php 19.678 2048 50.82%
127.0.0.1 - - [03/Apr/2024:12:47:45 +0000] "GET / HTTP/1.1" 500 3394 "http://localhost:8888/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
127.0.0.1 - - [03/Apr/2024:12:47:46 +0000] "GET /serviceWorker.js HTTP/1.1" 304 0 "http://localhost:8888/serviceWorker.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
127.0.0.1 - - [03/Apr/2024:12:47:47 +0000] "GET /serviceWorker.js HTTP/1.1" 304 0 "http://localhost:8888/serviceWorker.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
127.0.0.1 - - [03/Apr/2024:12:48:05 +0000] "GET /serviceWorker.js HTTP/1.1" 304 0 "http://localhost:8888/serviceWorker.js" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"

Rechecking the docs, we can see there is a manual DB Migrate one is expected to perform:

/content/images/2024/04/movary-02.png

I can do the equivalent by exec’ing into the app pod in Kubernetes and running the command

$ kubectl exec -it movary-deployment-59bc965c6c-hjzvc -n movary -- /bin/bash
movary-deployment-59bc965c6c-hjzvc:/app$ php bin/console.php database:migration:migrate
using config file settings/phinx.php
using config parser php
using migration paths
 - /app/db/migrations/mysql
warning no environment specified, defaulting to: dynamic
using adapter mysql
using database movary
ordering by creation time

 == 20210124104021 SetupBaseTables: migrating
 == 20210124104021 SetupBaseTables: migrated 0.0864s

 == 20210204195525 AddMoreMovieDetails: migrating
 == 20210204195525 AddMoreMovieDetails: migrated 0.1641s

 == 20210204211239 RemoveYearFromMovie: migrating
 == 20210204211239 RemoveYearFromMovie: migrated 0.0296s

 == 20210206222052 AddGenre: migrating
 == 20210206222052 AddGenre: migrated 0.0606s

 == 20220402194534 AddPerson: migrating
 == 20220402194534 AddPerson: migrated 0.0823s

 == 20220418073143 AddProductionCompanies: migrating
 == 20220418073143 AddProductionCompanies: migrated 0.0559s

 == 20220418081407 AddMovieTagline: migrating
 == 20220418081407 AddMovieTagline: migrated 0.0389s

 == 20220420173442 AddSecondRatingType: migrating
 == 20220420173442 AddSecondRatingType: migrated 0.0648s

 == 20220420185953 AddLetterboxdId: migrating
 == 20220420185953 AddLetterboxdId: migrated 0.0428s

 == 20220424171847 RemoveTimeFromWatchDate: migrating
 == 20220424171847 RemoveTimeFromWatchDate: migrated 0.0483s

 == 20220505182414 AddPosterPathToMovie: migrating
 == 20220505182414 AddPosterPathToMovie: migrated 0.0432s

 == 20220506184030 AddSyncLogTable: migrating
 == 20220506184030 AddSyncLogTable: migrated 0.0218s

 == 20220506195137 ChangePrimaryKeyInTraktCache: migrating
 == 20220506195137 ChangePrimaryKeyInTraktCache: migrated 0.0398s

 == 20220510185016 AddUser: migrating
 == 20220510185016 AddUser: migrated 0.0277s

 == 20220514093905 AddPlaysToHistory: migrating
 == 20220514093905 AddPlaysToHistory: migrated 0.0494s

 == 20220515085154 AddPosterPathToPerson: migrating
 == 20220515085154 AddPosterPathToPerson: migrated 0.0321s

 == 20220604103136 SplitUpMovePosterPath: migrating
 == 20220604103136 SplitUpMovePosterPath: migrated 0.1113s

 == 20220605074031 ChangeMovieTraktIdAndImdbIdToNullable: migrating
 == 20220605074031 ChangeMovieTraktIdAndImdbIdToNullable: migrated 0.1028s

 == 20220606175053 ChangeTraktIdToUniqueInWatchedCache: migrating
 == 20220606175053 ChangeTraktIdToUniqueInWatchedCache: migrated 0.0555s

 == 20220624095147 ReplaceRating10AndRating5WithPersonalRating: migrating
 == 20220624095147 ReplaceRating10AndRating5WithPersonalRating: migrated 0.0676s

 == 20220628192946 AddPlexWebhookId: migrating
 == 20220628192946 AddPlexWebhookId: migrated 0.0189s

 == 20220630135944 AddUserAuthTokenTable: migrating
 == 20220630135944 AddUserAuthTokenTable: migrated 0.0246s

 == 20220708144310 AddMultiUserSetup: migrating
 == 20220708144310 AddMultiUserSetup: migrated 0.3478s

 == 20220711194814 SetCorrectConstraintForMovieWatchDates: migrating
 == 20220711194814 SetCorrectConstraintForMovieWatchDates: migrated 0.0301s

 == 20220713163724 AddTraktClientIdToUserTable: migrating
 == 20220713163724 AddTraktClientIdToUserTable: migrated 0.0569s

 == 20220714094745 AddCacheTableForTmdbLanguages: migrating
 == 20220714094745 AddCacheTableForTmdbLanguages: migrated 0.0196s

 == 20220714115426 AddDateFormatToUserTable: migrating
 == 20220714115426 AddDateFormatToUserTable: migrated 0.0320s

 == 20220718170243 AddCoreAccountChangesDisabledToUser: migrating
 == 20220718170243 AddCoreAccountChangesDisabledToUser: migrated 0.0313s

 == 20220719134322 AddJobQueueTable: migrating
 == 20220719134322 AddJobQueueTable: migrated 0.0196s

 == 20220725113013 UpdateUsername: migrating
 == 20220725113013 UpdateUsername: migrated 0.0500s

 == 20220728103556 AddPrivacyLevelToUser: migrating
 == 20220728103556 AddPrivacyLevelToUser: migrated 0.0294s

 == 20220731091237 AddImdbRatingToMovie: migrating
 == 20220731091237 AddImdbRatingToMovie: migrated 0.1745s

 == 20220804080839 UpdateProductionCompanyForeignKeys: migrating
 == 20220804080839 UpdateProductionCompanyForeignKeys: migrated 0.1080s

 == 20220815113051 UpdateJobQueueTable: migrating
 == 20220815113051 UpdateJobQueueTable: migrated 0.0325s

 == 20220816164829 RemoveScanLog: migrating
 == 20220816164829 RemoveScanLog: migrated 0.0128s

 == 20221206190149 ExtendPersonMetaData: migrating
 == 20221206190149 ExtendPersonMetaData: migrated 0.0787s

 == 20221208101424 AddPlexScrobbleOptionsToUser: migrating
 == 20221208101424 AddPlexScrobbleOptionsToUser: migrated 0.0558s

 == 20221209195438 UpdateMoveForeignKeysToCascade: migrating
 == 20221209195438 UpdateMoveForeignKeysToCascade: migrated 0.2059s

 == 20221220113521 InitialSqliteAdjustment: migrating
 == 20221220113521 InitialSqliteAdjustment: migrated 0.2649s

 == 20221225093745 SetCastCharacterNameColumnToNullable: migrating
 == 20221225093745 SetCastCharacterNameColumnToNullable: migrated 0.0306s

 == 20221230153943 AddLetterboxdDiaryCacheTable: migrating
 == 20221230153943 AddLetterboxdDiaryCacheTable: migrated 0.0173s

 == 20230103172838 AddJellyfinWebhookId: migrating
 == 20230103172838 AddJellyfinWebhookId: migrated 0.0569s

 == 20230127104327 AddBackdropPathToMovieTable: migrating
 == 20230127104327 AddBackdropPathToMovieTable: migrated 0.0357s

 == 20230128101746 AddCommentToWatchDate: migrating
 == 20230128101746 AddCommentToWatchDate: migrated 0.0261s

 == 20230410104922 AddIsAdminToUserTable: migrating
 == 20230410104922 AddIsAdminToUserTable: migrated 0.0408s

 == 20230417183901 AddWatchlistTable: migrating
 == 20230417183901 AddWatchlistTable: migrated 0.0352s

 == 20230423165919 AddServerSettingTable: migrating
 == 20230423165919 AddServerSettingTable: migrated 0.0179s

 == 20230506194733 AddWatchlistAutomaticRemovalEnabledColumnToUserTable: migrating
 == 20230506194733 AddWatchlistAutomaticRemovalEnabledColumnToUserTable: migrated 0.0384s

 == 20230507095753 AddEmbyWebhookIdToUserTable: migrating
 == 20230507095753 AddEmbyWebhookIdToUserTable: migrated 0.0824s

 == 20230507185125 AddDashboardSettingsToUserTable: migrating
 == 20230507185125 AddDashboardSettingsToUserTable: migrated 0.1016s

 == 20230627162519 AddPlexOAuthColumnsToUserTable: migrating
 == 20230627162519 AddPlexOAuthColumnsToUserTable: migrated 0.1780s

 == 20230704133329 AddBiographyToPersonTable: migrating
 == 20230704133329 AddBiographyToPersonTable: migrated 0.0256s

 == 20230714115544 CreateTmdbIsoCountryCacheTable: migrating
 == 20230714115544 CreateTmdbIsoCountryCacheTable: migrated 0.0203s

 == 20230716185217 AddCountryColumnToUserTable: migrating
 == 20230716185217 AddCountryColumnToUserTable: migrated 0.0672s

 == 20230727132752 AddJellyfinAccessTokenColumn: migrating
 == 20230727132752 AddJellyfinAccessTokenColumn: migrated 0.1107s

 == 20230801150607 AddJellyfinSyncEnabledToUserTable: migrating
 == 20230801150607 AddJellyfinSyncEnabledToUserTable: migrated 0.0639s

 == 20230814190244 AddTotpSecretColumn: migrating
 == 20230814190244 AddTotpSecretColumn: migrated 0.0395s

 == 20230902112622 AddUserApiTokenTable: migrating
 == 20230902112622 AddUserApiTokenTable: migrated 0.0270s

 == 20230908191445 AddRadarrFeedUuidColumnToUserTable: migrating
 == 20230908191445 AddRadarrFeedUuidColumnToUserTable: migrated 0.0475s

 == 20230911074701 SetWatchDateNullableForUsers: migrating
 == 20230911074701 SetWatchDateNullableForUsers: migrated 0.0869s

 == 20230929140932 RemoveUniqueNameAndCountryConstraintForCompany: migrating
 == 20230929140932 RemoveUniqueNameAndCountryConstraintForCompany: migrated 0.0201s

 == 20231001110211 AddImdbIdToPersonTable: migrating
 == 20231001110211 AddImdbIdToPersonTable: migrated 0.0324s

 == 20231006174123 CreateUserPersonSettingsTable: migrating
 == 20231006174123 CreateUserPersonSettingsTable: migrated 0.0335s

 == 20231007144149 AddDisplayCharacterNamesToUserTable: migrating
 == 20231007144149 AddDisplayCharacterNamesToUserTable: migrated 0.0530s

 == 20231112151603 AddPositionToUserWatchDatesTable: migrating
 == 20231112151603 AddPositionToUserWatchDatesTable: migrated 0.0220s

All Done. Took 4.2013s

That fixed it

/content/images/2024/04/movary-03.png

Once my initial user was created, I could see the app

/content/images/2024/04/movary-04.png

Though trying to get to settings showed the error page and i got errors trying to add a movie

/content/images/2024/04/movary-05.png

That error showed in the logs

[php-fpm:access] 127.0.0.1 -  03/Apr/2024:13:04:22 +0000 "POST /index.php" 500 /app/public/index.php 199.105 2048 35.16%
127.0.0.1 - - [03/Apr/2024:13:04:22 +0000] "POST /settings/netflix/search HTTP/1.1" 500 11478 "http://localhost:8888/users/idjohnson/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
[2024-04-03T15:04:25.632165+02:00] movary.EMERGENCY: TMDB API key is probably not set or invalid. {"exception":"[object] (Movary\\Api\\Tmdb\\Exception\\TmdbAuthorizationError(code: 0): TMDB API key is probably not set or invalid. at /app/src/Api/Tmdb/Exception/TmdbAuthorizationError.php:11)"} []

That I didn’t have a proper key and indeed I had made a typo in the TMDB API Key field.

I fixed the API and applied. Now the movie add dialogue worked

/content/images/2024/04/movary-06.png

I added some movies I’ve seen and want to see


$ kubectl port-forward svc/movary-service -n movary 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80
Handling connection for 8888
Handling connection for 8888

/content/images/2024/04/movary-07.png

This looks good. I think it’s time to expose externally.

I’ll add an A Record

$ cat r53-movary.json
{
    "Comment": "CREATE movary fb.s A record ",
    "Changes": [
      {
        "Action": "CREATE",
        "ResourceRecordSet": {
          "Name": "movary.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-movary.json
{
    "ChangeInfo": {
        "Id": "/change/C05540171355MASD8TPK2",
        "Status": "PENDING",
        "SubmittedAt": "2024-04-03T21:11:06.233Z",
        "Comment": "CREATE movary fb.s A record "
    }
}

And then create an Ingress

builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ cat movary.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
    kubernetes.io/tls-acme: "true"
    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"
  labels:
    app.kubernetes.io/instance: movary
  name: movaryingress
  namespace: movary
spec:
  rules:
  - host: movary.freshbrewed.science
    http:
      paths:
      - backend:
          service:
            name: movary-service
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - movary.freshbrewed.science
    secretName: movary-tls
builder@DESKTOP-QADGF36:~/Workspaces/jekyll-blog$ kubectl apply -f ./movary.ingress.yaml -n movary
ingress.networking.k8s.io/movaryingress created

/content/images/2024/04/movary-08.png

Interesting, now that it has a proper DNS, the settings page seems to work

/content/images/2024/04/movary-09.png

I can setup Email now under settings

/content/images/2024/04/movary-10.png

which is easy enough to test

/content/images/2024/04/movary-11.png

Which I soon received

/content/images/2024/04/movary-12.png

I can also add a guest account

/content/images/2024/04/movary-13.png

Feel free to give it a try with guest@guest.com / guestoffreshbrewed

/content/images/2024/04/movary-14.png

Sharing

I only realized this later; you can share publicly your reviews.

You have to set the visibility in settings

/content/images/2024/04/movary-15.png

Then you can publish a URL like https://movary.freshbrewed.science/users/idjohnson/dashboard which shows my current movies and reviews

/content/images/2024/04/movary-16.png

Moodist

We reviewed Noisedash a while back. Let’s look at another noise app, Moodist

Let’s create a Kubernetes manifest

$ cat manifest.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: moodist-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: moodist
  template:
    metadata:
      labels:
        app: moodist
    spec:
      containers:
      - name: moodist
        image: ghcr.io/remvze/moodist
        resources:
          limits:
            memory: 4Gi
            cpu: 200m
        readinessProbe:
          httpGet:
            path: /
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        ports:
        - containerPort: 8080
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: moodist-service
spec:
  selector:
    app: moodist
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
  type: ClusterIP

Since we used AWS Route53 last time, let’s use Azure DNS for this app

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n moodist
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    }
  ],
  "TTL": 3600,
  "etag": "e9e527f7-3db7-4d02-8358-a75ec42ee7de",
  "fqdn": "moodist.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/moodist",
  "name": "moodist",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

I can now create an Ingress that uses my AzureDNS cluster issuer

$ cat ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.org/websocket-services: moodist-service
  name: moodist-ingress
spec:
  rules:
  - host: moodist.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: moodist-service
            port:
              number: 80
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - moodist.tpk.pw
    secretName: moodist-tls

And apply it

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

Since I was in a loud area, I tested using my phone

/content/images/2024/04/moodist-01.jpg

Frankly that sounded a lot like a campsite in the BWCA. I really got (am) relaxed.

I found airplane and brown noise was another good one. Though I generally just fire up this YouTube for Airplane Noise. The thing is i can hear a slight high pitch repeat on the app’s airplane noise and once I notice, it’s hard to un-notice.

Still, you are welcome to try https://moodist.tpk.pw/.

Summary

I wanted to share a couple apps that were on my radar, Moodist and Movery. Movary is a pretty nifty catalogue for tracking your movies and reviews.

Both can run quite easily in Docker but moving to Kubernetes let us handle TLS and route with DNS names.

Docker Kubernetes Moodist Movery OpenSource

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