I wanted to add comments to this blog without using a third-party service like Disqus. After evaluating options, I chose Comentario - a self-hosted comment system that uses PostgreSQL for storage. This post covers the full deployment on Kubernetes with HAProxy TLS termination and custom theming.
Why Comentario
When choosing a comment system, I had a few requirements:
- Self-hosted - No third-party data collection
- PostgreSQL backend - I already run a shared PostgreSQL instance, so no extra backup infrastructure needed
- GitHub OAuth - Most of my readers are developers
- Simple embed - Just a script tag and web component
I considered Remark42 (uses BoltDB) and Commento (abandoned), but Comentario hit all the marks. It’s an actively maintained fork of Commento with PostgreSQL support.
Architecture Overview
┌──────────────────┐ ┌─────────────────┐ ┌───────────────────┐
│ minoko.life │ │ HAProxy │ │ Comentario │
│ (Hugo blog) │─────▶│ (TLS + ACL) │─────▶│ (comments) │
│ │ │ 192.168.2.1 │ │ 192.168.2.223 │
└──────────────────┘ └─────────────────┘ └───────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
│ Hugo │ │ OPNsense │ │ PostgreSQL │
│ PaperMod │ │ (firewall) │ │ (shared) │
└─────────────┘ └──────────────┘ └───────────────┘
The key components:
- Comentario runs on Kubernetes with a LoadBalancer IP (192.168.2.223)
- HAProxy on OPNsense handles TLS termination and routes
comments.minoko.lifeto Comentario - PostgreSQL is a shared instance already used by other services
- Hugo embeds the Comentario web component on blog posts
Deploying Comentario on Kubernetes
Helm Chart Setup
Comentario provides an official Helm chart in their GitLab repository:
# Clone the chart
git clone --depth 1 https://gitlab.com/comentario/comentario.git comentario-chart
Helm Values
# comentario-values.yaml
replicaCount: 1
image:
repository: registry.gitlab.com/comentario/comentario
tag: latest
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
externalTrafficPolicy: Local
annotations:
metallb.universe.tf/loadBalancerIPs: "192.168.2.223"
ingress:
enabled: false # Using LoadBalancer + HAProxy instead
secret:
name: comentario-secrets
comentario:
baseUrl: "https://comments.minoko.life"
baseDocsUrl: "https://docs.comentario.app"
nodeSelector:
kubernetes.io/arch: amd64 # Comentario only supports amd64
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
Secrets Configuration
Comentario expects a secrets.yaml file with database credentials and OAuth config:
# secrets.yaml (create as Kubernetes secret)
postgres:
host: postgresql.postgres.svc.cluster.local
port: 5432
database: comentario
username: comentario
password: <your-password>
sslMode: disable
idp:
github:
key: <github-client-id>
secret: <github-client-secret>
xsrfSecret: "<random-32-char-string>"
Create the secret:
kubectl create secret generic comentario-secrets \
--from-file=secrets.yaml \
-n comentario
Database Setup
Since I use a shared PostgreSQL instance, I created the database manually:
kubectl exec -n postgres postgresql-0 -- psql -U postgres << 'EOF'
CREATE DATABASE comentario;
CREATE USER comentario WITH PASSWORD 'secure-password';
GRANT ALL PRIVILEGES ON DATABASE comentario TO comentario;
ALTER DATABASE comentario OWNER TO comentario;
EOF
Deploy
helm upgrade --install comentario \
comentario-chart/resources/helm/comentario \
-n comentario --create-namespace \
-f comentario-values.yaml
HAProxy TLS Termination
Rather than handling TLS in Kubernetes, I route through OPNsense’s HAProxy for certificate management. This keeps all public-facing TLS in one place.
Creating the Backend
Using the OPNsense API:
# Create backend pointing to Comentario's LoadBalancer IP
curl -X POST "https://opnsense.lan/api/haproxy/settings/addBackend" \
-d '{
"backend": {
"enabled": "1",
"name": "comentario_backend",
"mode": "http",
"algorithm": "roundrobin"
}
}'
Creating the Server
# Add server to backend
curl -X POST "https://opnsense.lan/api/haproxy/settings/addServer" \
-d '{
"server": {
"name": "comentario_server",
"address": "192.168.2.223",
"port": "80",
"checkEnabled": "1"
}
}'
Host-Based ACL
Route requests for comments.minoko.life to the Comentario backend:
# Create ACL
curl -X POST "https://opnsense.lan/api/haproxy/settings/addAcl" \
-d '{
"acl": {
"name": "host_comments",
"expression": "hdr(host)",
"hdr": "comments.minoko.life"
}
}'
# Create use_backend action
curl -X POST "https://opnsense.lan/api/haproxy/settings/addAction" \
-d '{
"action": {
"name": "use_comentario",
"testType": "if",
"linkedAcls": "<acl-uuid>",
"operator": "and",
"actionName": "use_backend",
"useBackend": "<backend-uuid>"
}
}'
DNS
Point the DNS record to HAProxy instead of the Kubernetes LoadBalancer:
comments.minoko.life -> 192.168.2.1 (OPNsense HAProxy)
Hugo Integration
The Comments Partial
Create layouts/partials/comments.html:
{{- /* Comentario Comments Integration */ -}}
{{- /* https://docs.comentario.app/en/configuration/embedding/ */ -}}
<comentario-comments css-override="/css/comentario-theme.css" no-fonts="true"></comentario-comments>
<script defer src="https://comments.minoko.life/comentario.js"></script>
Key attributes:
css-override- Path to custom theme CSSno-fonts- Use the blog’s fonts instead of Comentario’s
Enable in hugo.toml
[params]
comments = true
The PaperMod theme automatically includes the comments partial when this is enabled.
Custom Theming
Comentario exposes CSS custom properties for theming. I created a stylesheet that matches PaperMod’s warm beige/brown color scheme.
Light Theme
/* static/css/comentario-theme.css */
comentario-comments {
--cmntr-bg: var(--theme, #faf8f5);
--cmntr-bg-shade: var(--tertiary, #e8e2da);
--cmntr-color: var(--primary, #3d3229);
--cmntr-link-color: #b8632e;
--cmntr-link-hover-color: #d4763a;
--cmntr-muted-color: var(--secondary, #7a6f63);
--cmntr-card-border: var(--border, #e0d8cc);
--cmntr-input-bg: var(--entry, #ffffff);
--cmntr-input-color: var(--content, #4a4139);
}
Dark Theme
@media (prefers-color-scheme: dark) {
comentario-comments:not([theme]) {
--cmntr-bg: var(--theme, #1d1e20);
--cmntr-bg-shade: var(--entry, #2e2e33);
--cmntr-color: var(--primary, #dadada);
--cmntr-link-color: #e09556;
--cmntr-link-hover-color: #f0a566;
--cmntr-muted-color: var(--secondary, #9b9c9d);
--cmntr-card-border: var(--border, #333333);
--cmntr-input-bg: var(--entry, #2e2e33);
}
}
Button Styling
/* Primary button - orange accent */
.comentario-root .comentario-btn-primary {
--cmntr-btn-color: #ffffff;
--cmntr-btn-bg: #b8632e;
--cmntr-btn-hover-color: #ffffff;
--cmntr-btn-hover-bg: #d4763a;
}
/* GitHub button - dark brown */
.comentario-root .comentario-btn-github {
--cmntr-btn-color: #ffffff;
--cmntr-btn-bg: #3d3229;
--cmntr-btn-hover-color: #ffffff;
--cmntr-btn-hover-bg: #5a4a3d;
}
The result is a comment section that looks native to the blog in both light and dark modes.
GitHub OAuth Setup
Create OAuth App
- Go to GitHub Settings > Developer settings > OAuth Apps
- Create new OAuth App:
- Application name: Minoko Life Comments
- Homepage URL: https://minoko.life
- Authorization callback URL: https://comments.minoko.life/api/oauth/github/callback
Configure in Comentario
Add the Client ID and Secret to your secrets.yaml:
idp:
github:
key: Ov23li...
secret: abc123...
Admin Configuration
In the Comentario admin panel (https://comments.minoko.life):
- Create your admin account on first visit
- Add domain:
minoko.life - Disable local signup (GitHub OAuth only)
- Configure moderation settings as needed
Troubleshooting
Comments Not Appearing
If the comment widget doesn’t render, check:
- Correct embed element - Use
<comentario-comments>not<div id="comentario">(Comentario uses a web component) - Script loading - Check browser console for 404s on
comentario.js - CORS - Ensure
baseUrlin Comentario config matches the actual URL
HTTPS Issues
If comments load but authentication fails:
- Check
baseUrlis set tohttps://in Comentario config - Verify HAProxy is terminating TLS and passing
X-Forwarded-Protoheader - Check OAuth callback URL matches exactly
Database Connection
# Check Comentario logs
kubectl logs -n comentario deployment/comentario
# Test PostgreSQL connectivity
kubectl exec -n comentario deployment/comentario -- \
psql "postgresql://comentario:[email protected]/comentario" \
-c "SELECT 1"
Conclusion
With Comentario deployed, I now have:
- Self-hosted comments with no third-party dependencies
- PostgreSQL storage (shared with other services, single backup strategy)
- GitHub OAuth for developer-friendly authentication
- Custom theming that matches the blog perfectly
- TLS termination through existing HAProxy infrastructure
The whole setup took a few hours, most of which was getting the HAProxy ACLs right via the OPNsense API. Once deployed, it’s been maintenance-free.
Feel free to test it out by leaving a comment below!