Tiny Tiny RSS (tt-rss) is an open-source RSS reader that can be run on your home server. It offers many excellent features including synchronization across multiple devices, a customizable interface, and extensibility through plugins.
I mainly use an RSS reader for two reasons:
Selective information intake: In today’s information-overloaded environment, receiving information only from sources I’ve chosen prevents mental fatigue.
Avoiding temptations: For example, subscribing to YouTube channels via RSS allows me to avoid recommended videos. This eliminates the need to fight against temptations.
I chose tt-rss because there’s no risk of service termination and it can be run on a home server. While FreshRSS is another self-hosted RSS reader option, tt-rss excels in terms of extensibility.
While information about deploying tt-rss using Helm charts (Kubernetes package management tool) can be found, there’s still limited information about writing Kubernetes manifests from scratch. Therefore, I’ll explain how to deploy tt-rss to a K8s cluster in the following order:
System configuration and environment explanation
MySQL database deployment
tt-rss application deployment
Nginx reverse proxy deployment
This article uses container images compatible with the ARM64 architecture of Raspberry Pi, but you can apply it to other architectures simply by changing the images.
Configuration files can be created separately or combined into one. If you want to set everything up at once, refer to the Appendix section at the end of the article. This article introduces the minimum configuration required, so feel free to customize it to suit your environment.
The main Kubernetes resources used for deployment are as follows:
Namespace: Logically groups resources, separating tt-rss and Nginx management
Deployment: Manages and scales application pods
Service: Provides access points to pods, ensuring stable communication
PersistentVolumeClaim: Persists database data
Secret: Securely manages sensitive information such as passwords
System Configuration
The system we’re building consists of the following components:
Figure 1: Kubernetes deployment configuration for tt-rss
Nginx reverse proxy: Accepts external access and forwards to tt-rss
tt-rss application: The RSS reader itself
MySQL database: Stores tt-rss data
External access reaches tt-rss through Nginx, and tt-rss stores data in MySQL. Each component runs as a separate pod and connects via Services, allowing for individual updates and scaling.
MySQL Deployment
Creating a Namespace
First, create a namespace to logically separate tt-rss-related resources. This makes resource management easier and avoids conflicts with other applications.
# ttrss-namespace.yaml# Namespace for logically grouping tt-rss-related resourcesapiVersion: v1kind: Namespacemetadata:
name: ttrss # tt-rss and MySQL resources will be placed in this namespace
Database Connection Secret
Manage database passwords as Secrets. This improves security by avoiding plaintext passwords in manifest files.
# Create Secret for MySQL root password and tt-rss user passwordkubectl create secret generic mysql-secret \
--from-literal=mysql_root_pass=rootpassword \
--from-literal=mysql_ttrss_pass=ttrsspassword -n ttrss
MySQL Database PVC
Create a PersistentVolumeClaim to persist database data. This ensures data isn’t lost when pods restart.
# mysql-pvc.yaml# PersistentVolumeClaim for persisting MySQL dataapiVersion: v1kind: PersistentVolumeClaimmetadata:
namespace: ttrss # placed in ttrss namespacename: mysql-pvc # this PVC will be referenced by the MySQL deploymentspec:
accessModes:
- ReadWriteOnce # allows read-write access from a single noderesources:
requests:
storage: 1Gi # requests 1GB of storage (can be adjusted as needed)
MySQL Deployment
Deploy the MySQL database to the Kubernetes cluster. This deployment uses an ARM64-compatible MySQL image and configures the necessary environment variables and volume mounts.
# mysql-deployment.yaml# Manifest for deploying MySQL databaseapiVersion: apps/v1kind: Deploymentmetadata:
namespace: ttrss # placed in ttrss namespacename: mysql # deployment namespec:
replicas: 1# MySQL runs as a single instance (not a replication configuration)selector:
matchLabels:
app: mysql # manages pods with this labeltemplate:
metadata:
labels:
app: mysql # applies label to podspec:
containers:
- name: mysqlimage: arm64v8/mysql:8.0 # MySQL image for ARM64 architectureenv:
# MySQL configuration via environment variables - name: MYSQL_ROOT_PASSWORD # root password (retrieved from Secret)valueFrom:
secretKeyRef:
name: mysql-secretkey: mysql_root_pass - name: MYSQL_DATABASEvalue: ttrss # database name for tt-rss - name: MYSQL_USERvalue: ttrss # database user for tt-rss - name: MYSQL_PASSWORD # user password (retrieved from Secret)valueFrom:
secretKeyRef:
name: mysql-secretkey: mysql_ttrss_passports:
- containerPort: 3306# standard MySQL portvolumeMounts:
- name: mysql-storage # volume for data persistencemountPath: /var/lib/mysql # MySQL data directoryvolumes:
- name: mysql-storagepersistentVolumeClaim:
claimName: mysql-pvc # uses the PVC created earlier
MySQL Service
Create a service to access MySQL. This service enables the tt-rss application to access the database reliably.
# mysql-service.yaml# Service definition for accessing MySQLapiVersion: v1kind: Servicemetadata:
namespace: ttrss # placed in ttrss namespacename: mysql # service name (accessible within the cluster via this DNS name)spec:
selector:
app: mysql # targets mysql podsports:
- port: 3306# standard MySQL porttype: ClusterIP # accessible only within the cluster
tt-rss Deployment
tt-rss Deployment
Deploy the tt-rss application itself. This deployment configures the tt-rss container and the environment variables needed for database connection.
# ttrss-deployment.yaml# Manifest for deploying tt-rss applicationapiVersion: apps/v1kind: Deploymentmetadata:
namespace: ttrss # placed in ttrss namespacename: ttrss # deployment namespec:
replicas: 1# deployed as a single instanceselector:
matchLabels:
app: ttrss # manages pods with this labeltemplate:
metadata:
labels:
app: ttrss # applies label to podspec:
containers:
- name: ttrssimage: nventiveux/ttrss # tt-rss container imageenv:
# Database connection information set via environment variables - name: TTRSS_DB_HOSTvalue: mysql # MySQL service name - name: TTRSS_DB_PORTvalue: "3306"# MySQL port - name: TTRSS_DB_NAMEvalue: ttrss # database name - name: TTRSS_DB_USERvalue: ttrss # database user - name: TTRSS_DB_PASS # database password (retrieved from Secret)valueFrom:
secretKeyRef:
name: mysql-secretkey: mysql_ttrss_pass - name: TTRSS_DB_TYPEvalue: mysql # database type - name: TTRSS_SELF_URL_PATHvalue: "http://IP_Address:30080/"# external access URL for tt-rss (change to match your environment)ports:
- containerPort: 80# tt-rss web server port
tt-rss Service
Create a service to access tt-rss. This service enables communication from Nginx to tt-rss.
# ttrss-service.yaml# Service definition for accessing tt-rss applicationapiVersion: v1kind: Servicemetadata:
namespace: ttrss # placed in ttrss namespacename: ttrss # service name (accessible within the cluster via this DNS name)spec:
selector:
app: ttrss # targets ttrss podsports:
- name: httpport: 80# service porttargetPort: 80# container portprotocol: TCPtype: ClusterIP # accessible only within the cluster
Nginx Deployment
Creating a Namespace
Create a dedicated namespace for separate management of Nginx resources. This allows management separate from tt-rss.
# nginx-namespace.yaml# Namespace for separate management of Nginx resourcesapiVersion: v1kind: Namespacemetadata:
name: nginx # namespace for Nginx-related resourceslabels:
app: nginxrole: reverse-proxy # indicates role as reverse proxy
Nginx Configuration ConfigMap
Manage Nginx configuration as a ConfigMap. This allows flexible updates to Nginx settings and separates deployment from configuration.
# nginx-configmap.yaml# Manage Nginx configuration as ConfigMapapiVersion: v1kind: ConfigMapmetadata:
namespace: nginx # placed in nginx namespacename: nginx-config # ConfigMap namedata:
default.conf: | server {
listen 80;
server_name localhost;
# Reverse proxy settings for tt-rss
location / {
# Forward requests to tt-rss service using internal DNS
proxy_pass http://ttrss.ttrss.svc.cluster.local:80/;
# Header settings to properly convey client information to tt-rss
proxy_set_header Host $host:30080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Nginx Deployment
Deploy Nginx. This deployment runs the Nginx container and mounts the ConfigMap created earlier as a configuration file.
# nginx-deployment.yaml# Manifest for deploying Nginx reverse proxyapiVersion: apps/v1kind: Deploymentmetadata:
namespace: nginx # placed in nginx namespacename: nginx # deployment namelabels:
app: nginxspec:
replicas: 1# deployed as a single instance (can be increased if high availability is needed)selector:
matchLabels:
app: nginx # manages pods with this labeltemplate:
metadata:
labels:
app: nginx # applies label to podspec:
volumes:
- name: nginx-config # volume for configuration filesconfigMap:
name: nginx-config # references the ConfigMap created earliercontainers:
- name: nginximage: nginx:latest # uses official Nginx imageports:
- containerPort: 80# standard HTTP port for NginxvolumeMounts:
- name: nginx-config # mount ConfigMapmountPath: /etc/nginx/conf.d/ # Nginx configuration directory
Nginx Service
Create an Nginx service and expose it with NodePort. This allows access to tt-rss from outside the cluster.
# nginx-service.yaml# Manifest for exposing Nginx service externallyapiVersion: v1kind: Servicemetadata:
namespace: nginx # placed in nginx namespacename: nginx-service # service namespec:
selector:
app: nginx # targets nginx podsports:
- port: 80# service porttargetPort: 80# container portnodePort: 30080# exposed externally on node's port 30080type: NodePort # published as NodePort type service
Accessing tt-rss
After completing the deployment, you can access tt-rss at the following URL:
http://<node-IP>:30080/
On first access, you can log in with the default username “admin” and password “password”.
Figure 2: tt-rss login screen
If it doesn’t work properly, check the details using the kubectl describe pod or kubectl logs commands.
Appendix: All-in-One Configuration
Nginx manifest
# nginx-all.yaml# Manifest defining all Nginx-related resources at once# Namespace for NginxapiVersion: v1kind: Namespacemetadata:
name: nginxlabels:
app: nginxrole: reverse-proxy---
# ConfigMap for Nginx configurationapiVersion: v1kind: ConfigMapmetadata:
namespace: nginx name: nginx-configdata:
default.conf: | server {
listen 80;
server_name localhost;
# Reverse proxy settings for tt-rss
location / {
# Forward requests to tt-rss service using internal DNS
proxy_pass http://ttrss.ttrss.svc.cluster.local:80/;
# Header settings to properly convey client information to tt-rss
proxy_set_header Host $host:30080;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
} ---
# Nginx deploymentapiVersion: apps/v1kind: Deploymentmetadata:
namespace: nginx name: nginxlabels:
app: nginxspec:
replicas: 1# can be increased if high availability is neededselector:
matchLabels:
app: nginxtemplate:
metadata:
labels:
app: nginxspec:
volumes:
- name: nginx-configconfigMap:
name: nginx-configcontainers:
- name: nginximage: nginx:latestports:
- containerPort: 80volumeMounts:
- name: nginx-configmountPath: /etc/nginx/conf.d/---
# Nginx service (exposed externally via NodePort)apiVersion: v1kind: Servicemetadata:
namespace: nginx name: nginx-servicespec:
selector:
app: nginxports:
- port: 80targetPort: 80nodePort: 30080type: NodePort