Wallabag is an open-source, self-hostable read it later application similar to Mozilla Pocket that I discovered recently thanks to the r/selfhosted Reddit community. I used Pocket for a couple of years and I was happy with it, but now, I’m replacing as many online services as I can with open source alternatives to regain control over my data which is whereWallabag comes in. Wallabag runs in a server, has a mobile app, browser extension and makes it easy to import my data from Pocket. Perfect combination. In this post, I’ll show you how I deployed it to my Kubernetes cluster.
Credential Set Up
Wallabag stores its data in a SQL database that requires a username and password. To avoid hard-coding database credentials in the code, I created credentials in AWS Parameter Store using the AWS CLI so I could reference them from the code. If you’re interested in AWS security, checkout my post on it.
aws ssm put-parameter
--name "/K8s/Wallabag/wallabag-credentials"
--type "SecureString"
--value '{"SYMFONY __ENV__ DATABASE_PASSWORD": "your_password", "SYMFONY __ENV__ DATABASE_USER": "your_db_user"}'
The command above creates an encrypted secret in the AWS Parameter Store. Next, to sync the secret to Kubernetes, I created an ExternalSecret
object that references the AWS Secret and syncs it to a local secret called wallabag-container-env
:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: wallabag-external-secret
namespace: wallabag
spec:
refreshInterval: 1h
kind: SecretStore
target:
name: wallabag-container-env
creationPolicy: Owner
data:
- secretKey: SYMFONY __ENV__ DATABASE_PASSWORD
remoteRef:
key: /K8s/Wallabag/wallabag-credentials
property: SYMFONY __ENV__ DATABASE_PASSWORD
- secretKey: SYMFONY __ENV__ DATABASE_USER
remoteRef:
key: /K8s/Wallabag/wallabag-credentials
property: SYMFONY __ENV__ DATABASE_USER
External Secrets is a third-party Kubernetes operator that retrieves secrets from secure vaults and syncs them to Kubernetes secrets automatically.
Create Kubernetes Objects
After defining the credentials, I created all the other resources needed to deploy the application using this manifests I’ll share below. If you’d like to see the full YAML manifest, check it out in GitHub.
Namespace and ConfigMap
The first two resources define a namespace and configmap. A namespace in Kubernetes is a logical division in the cluster that helps group and isolate resources that must be deployed together. The ConfigMap is a way of injecting configuration data like environment variables and settings into containers. Here, I create the Wallabag namespace to containe all Wallabag resources and pass environment variables for connecting to the postgres database.
---
apiVersion: v1
kind: Namespace
metadata:
name: wallabag
---
apiVersion: v1
kind: ConfigMap
metadata:
name: wallabag-configmap
namespace: wallabag
data:
SYMFONY __ENV__ DATABASE_PORT: "5432"
SYMFONY __ENV__ DATABASE_DRIVER: pdo_pgsql
SYMFONY __ENV__ DATABASE_NAME: wallabag
SYMFONY __ENV__ DATABASE_HOST: wallabag-db
SYMFONY __ENV__ DOMAIN_NAME: "http://wallabag.ndlovucloud.co.zw"
---
Deployment and Networking
Next, I set up an ingress, a service and a deployment. An ingress acts as a gateway that routes traffic based on hostname, the service allows external traffic into the cluster and the deployment manages running containers. I reference the database credentials I created above as a secret in the deployment:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wallabag-ingress
namespace: wallabag
annotations:
gethomepage.dev/description: Wallabag
gethomepage.dev/enabled: "true"
gethomepage.dev/group: Cluster Management
gethomepage.dev/icon: wallabag.png
gethomepage.dev/name: Wallabag
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
ingressClassName: nginx
rules:
- host: wallabag.ndlovucloud.co.zw
http:
paths:
- path: /?(.*)
pathType: ImplementationSpecific
backend:
service:
name: wallabag
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: wallabag
namespace: wallabag
spec:
ports:
- protocol: TCP
port: 8083
targetPort: 80
name: http
selector:
app: wallabag
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wallabag
namespace: wallabag
spec:
replicas: 1
selector:
matchLabels:
app: wallabag
template:
metadata:
labels:
app: wallabag
spec:
containers:
- name: wallabag-web
image: wallabag/wallabag:2.6.10
resources:
requests:
memory: 128Mi
cpu: 100m
limits:
memory: 256Mi
cpu: 200m
ports:
- containerPort: 80
protocol: TCP
envFrom:
- configMapRef:
name: wallabag-configmap
- secretRef:
name: wallabag-container-env
I didn’t set up TLS certificates directly in Kubernetes for this project because I’ll use Cloudflare Tunnels to expose it and Cloudflare sets up automatic HTTPS for free.
Database
Next, I configured the database using a StatefulSet and a headless service. Statefulsets are similar to deployments but are suited for stateful objects like databases. They ensure that data isn’t lost when pods or containers are stopped or restarted. Headless services allow applications to communicate directly with the database instance.
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: wallabag-db
namespace: wallabag
spec:
selector:
matchLabels:
app: wallabag-db
serviceName: wallabag-db
replicas: 1
template:
metadata:
labels:
app: wallabag-db
spec:
containers:
- name: wallabag-db
image: postgres:13
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: wallabag-container-env
key: SYMFONY __ENV__ DATABASE_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: wallabag-container-env
key: SYMFONY __ENV__ DATABASE_PASSWORD
- name: POSTGRES_DB
value: wallabag
volumeClaimTemplates:
- metadata:
name: postgres-storage
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: local-path
resources:
requests:
storage: 1Gi
ordinals:
start: 1
---
apiVersion: v1
kind: Service
metadata:
name: wallabag-db
namespace: wallabag
spec:
ports:
- port: 5432
targetPort: 5432
protocol: TCP
selector:
app: wallabag-db
clusterIP: None
Outcome And Problems
After writing all the manifests, I committed them to git and pushed them to the repo and Flux CD deployed everything to the cluster. It took a bit of tinkering and wrestling with the YAML to get everything to deploy correctly, but after it did, I was met by this cryptic HTTP 500 error when I tried to open the application:
The logs showed that there were missing tables and relations in the database, database migrations didn’t get applied. After a quick Google search,I learnedthat the fix was to drop into the wallabag container shell and run the installation manually and fix file permissions afterwards:
kubectl exec -it wallabag-dcd545998-d9h47 -n wallabag sh
php bin/console wallabag:install --env=prod -n
# Fix file permissions, takes a few minutes to complete
chown -R nobody:nobody /var/www/wallabag
The two commands took a couple of minutes to complete but after running them, I was able to login to Wallabag
Conclusion
I like Wallabag for its comfortable reader mode, mobile app integration and the ease of importing data into it. On the downside, some aspects of it feel a little unrefined compared to Pocket,but that’s a small price to pay to be in full control over my own data.
Top comments (0)