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:

  1. Self-hosted - No third-party data collection
  2. PostgreSQL backend - I already run a shared PostgreSQL instance, so no extra backup infrastructure needed
  3. GitHub OAuth - Most of my readers are developers
  4. 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.life to 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 CSS
  • no-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

  1. Go to GitHub Settings > Developer settings > OAuth Apps
  2. Create new OAuth App:

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):

  1. Create your admin account on first visit
  2. Add domain: minoko.life
  3. Disable local signup (GitHub OAuth only)
  4. Configure moderation settings as needed

Troubleshooting

Comments Not Appearing

If the comment widget doesn’t render, check:

  1. Correct embed element - Use <comentario-comments> not <div id="comentario"> (Comentario uses a web component)
  2. Script loading - Check browser console for 404s on comentario.js
  3. CORS - Ensure baseUrl in Comentario config matches the actual URL

HTTPS Issues

If comments load but authentication fails:

  1. Check baseUrl is set to https:// in Comentario config
  2. Verify HAProxy is terminating TLS and passing X-Forwarded-Proto header
  3. 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!