3
apache tika
Justin Klein edited this page 2026-10-14 14:57:00 +00:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

apache-tika

Deploys the Apache Tika server, a content-extraction service that turns documents into text and metadata. Deployment and Service only — no Ingress, and none should be added. In this cluster it is a backend for paperless-ngx, which points PAPERLESS_TIKA_ENDPOINT at its Service.

Image apache/tika
Versions not listed on purpose — read the current pair off apache-tika/Chart.yaml, see Chart index
Workloads Deployment (1 replica)
Storage none — one emptyDir on /tmp
Ingress no — ClusterIP Service only
External dependencies none
Secrets none
Install-test ci/apache-tika/ (empty test values, no fixtures)

Prerequisites

None beyond a cluster. No vault item, no database, no volume, no ingress controller. The chart is installable with zero values.

Installation

The registry URL and its credentials live in 1Password (item jlab-registry-access: username, password, charts-url) and never in a repository or a shell history file.

helm repo add jlab "$CHARTS_URL" --username "$REG_USER" --password "$REG_PASS"
helm repo update

helm install tika jlab/apache-tika \
  --namespace documents --create-namespace

helm install prints the chart's NOTES.txt, which renders the same information — including the effective memory limit and the heap derived from it — against the values actually set. Read it — it is the per-release version of this page.

How it is reached, and by whom

There is no Ingress and no certificate. The server is cluster-internal, on a ClusterIP Service:

http://<release>-apache-tika-service.<namespace>.svc.cluster.local

Mind the port. The Service listens on 80 and forwards to the container's 9998, so consumers are configured without a port — e.g. paperless-ngx:

PAPERLESS_TIKA_ENDPOINT: http://<release>-apache-tika-service.<namespace>.svc.cluster.local

From your own machine:

kubectl -n <namespace> port-forward svc/<release>-apache-tika-service 9998:80
curl http://localhost:9998/version
curl http://localhost:9998/parsers

/parsers lists every parser the running server actually has — the honest answer to "why did this file come back empty".

Configurable values

Global

Key Type Default Description
scaleDown bool false Renders every workload with replicas: 0 without uninstalling. Takes precedence over tika.replicas. The Service keeps its ClusterIP but loses its endpoints, so callers fail with "connection refused" rather than a timeout.
global object unset Helm's conventional cross-chart values. Unread by this chart; kept open in the schema so an umbrella install cannot break on it.

tika

Note the key: the values section is tika, not the chart name apache-tika.

Key Type Default Description
tika.replicas int 1 Replica count; 0 is allowed. The server is stateless, so raising it is safe and is the way to serve concurrent extractions.
tika.securityContext.pod object non-root 35002:35002, seccompProfile: RuntimeDefault Passed verbatim into the pod spec; 35002 is the dedicated user of the official image. The profile was verified against a real extraction, not just a reachable port: the -full image loads its parsers dynamically, so a filter that blocked one would show up as HTTP 200 with empty text rather than as an error.
tika.securityContext.container object readOnlyRootFilesystem: true, runAsNonRoot: true, all caps dropped, no privilege escalation Passed verbatim into the container spec. Tika writes temporary files while parsing, so /tmp is a writable emptyDir.
tika.livenessProbe object GET / on port tika-port, period 10s, timeout 5s, 3 failures Tika answers GET / on its server port with 200; no external dependency hangs on it.
tika.readinessProbe object GET / on port tika-port, period 10s, timeout 5s, 3 failures
tika.startupProbe object GET / on port tika-port, period 5s, timeout 5s, 30 failures (= 150s) Gates liveness and readiness.
tika.env list [] Extra environment variables. Rendered through tpl; value, secretKeyRef and configMapKeyRef forms. Additive, not overriding — see Environment variables.
tika.resources.limitFactor number 3 Memory and ephemeral-storage limits are computed as factor × request. The memory limit is also what sizes the JVM heap — see below.
tika.resources.requests object cpu: 0.1, memory: 512Mi, ephemeralStorage: 10Mi
tika.resources.limits object unset (computed: memory: 1536Mi, ephemeral-storage: 30Mi) An explicit limit always wins. A CPU limit is only rendered when set here.

An erased block (tika.securityContext: with no children, a null probe, a null resources) falls back to the chart's own defaults instead of disappearing — style guide §12.2.

This chart has no ingress section at all: there is no ingress.domain and no ingress.clusterIssuer to set.

networkPolicy

Inbound 9998 only. Outbound DNS and nothing else: Tika is handed documents and fetches nothing — a property worth keeping, because a text extractor that can reach the internet is a worse thing to hand an untrusted document to. There is no ingressController value, because this chart renders no Ingress.

Value Type Default Meaning
networkPolicy.enabled bool true Renders the policy. Only a boundary where the CNI enforces one — unenforced, the API server accepts it and nothing is filtered, so everything reads as configured.
networkPolicy.allowedNamespaces list [] Namespaces whose pods may reach the restricted ports, additionally to the release's own. Matched on the kubernetes.io/metadata.name label Kubernetes sets itself — no labelling work, and no typo protection either: a name that does not exist matches nothing and is silently ineffective. Namespace-granular, so an entry opens the port to every pod in it.
networkPolicy.probeSources list [] CIDRs allowed to reach the probed port. Empty means open, not blocked — the rule is then rendered with no from at all. Probes come from the IP of the worker node running the pod, which the chart cannot know; a list that misses it drops every probe and the pod never becomes Ready.
networkPolicy.egress.extraPorts list [] Additional outbound TCP ports, to any destination. The escape hatch from the egress cap, and the only alternative to switching the policy off.

Operating notes

No authentication — and it parses whatever it is sent

Tika has no authentication and this chart adds none. It is a parser for untrusted input by nature, and its parsers have a long history of being the soft spot. Anything that can reach the Service can make this pod open a file. Keep it cluster-internal; do not put an Ingress in front of it.

The image tag has to keep its -full suffix

appVersion is pinned to 3.3.1.0-full. The -full variant is the one that ships Tesseract OCR (5.5.0, with the languages deu, eng, fra, ita, jpn, osd, spa) plus the extra parsers; the plain apache/tika:<version> image has no tesseract binary at all. Overriding the tag without -full does not fail loudly — OCR simply stops happening and scanned documents come back with empty text.

That is also why Renovate's block for this chart overrides versioning: the four-part version plus flavour suffix is parsed by an explicit regex whose compatibility group keeps the chart on -full. See Renovate.

The JVM only gets a quarter of the memory limit

The server starts without -Xmx, so the JVM derives its maximum heap from the container limit at MaxRAMPercentage=25. With the default limit of 1536Mi that is roughly 384Mi of usable heap — the number to reason about when sizing, not the limit itself. Raising tika.resources.requests.memory raises the limit (limitFactor × request) and the heap along with it.

Text extraction streams rather than buffering, so ordinary documents stay cheap regardless. The heap matters for parsers that have to build a whole document in memory, and for OCR.

An empty result is a parser question, not a server question

A 200 with empty text is the normal shape of every failure mode here: a missing parser, a tag without -full, a scanned file without OCR. curl /parsers against the running pod lists what it actually has; /version confirms which image is behind the Service.

This chart has no Ingress, and its consumers are other namespaces

Tika is cluster-internal: its consumers are pods, and in this cluster they are paperless-ngx releases in their own namespaces. While probeSources is empty the 9998 rule has no source selector, so they reach it. Narrowing probeSources restricts that rule, and those namespaces must go into networkPolicy.allowedNamespaces in the same change — otherwise every document extraction stops, with a connection error on the paperless side and nothing at all on this one.

The probed port is open inside the cluster by default

networkPolicy.probeSources is empty out of the box, so the rule for the port the probes address is rendered with no from selector at all — any pod the CNI routes to can reach it. Not from the internet (the port carries no NodePort), but this is not pod-level isolation, and reading the object as if it were is the mistake to avoid.

It is that way on purpose. The kubelet's liveness, readiness and startup probes originate from the IP of the worker node running the pod — an address no chart can know and must not guess. A CIDR that excludes the real node blocks every probe: the pod never becomes Ready, the rollout times out, and the symptom reads as a broken application rather than as a policy. Shipping open is the honest default; narrowing it means listing the worker node IPs yourself.

Note the companion trap: the moment probeSources is non-empty the rule is restricted, so everything else that needs the port has to be in the same rule. The chart adds the release's own namespace, ingressController and allowedNamespaces for exactly that reason.

The policy is only a boundary where the CNI enforces one

The API server accepts a NetworkPolicy whatever the cluster does with it. Where the CNI does not enforce policies, everything looks configured and nothing is filtered — which is worse than no policy, because it invites the assumption that something is being filtered. Verify by connecting from an unrelated namespace rather than by reading it off the manifest.

Measured for this cluster: the CNI is Canal (Flannel for the network, Calico for policy), 6/6, and it does enforce. Measured for CI: k3s brings its own policy controller and enforces too — but it exempts the kubelet's probe traffic, so a blocked probe path does not turn the install-test red. What CI does catch is anything the application itself needs.

Upgrades and backup

Nothing to back up and nothing to migrate: no volume, no database, no state that survives a pod. An app-version bump is an image swap, and helm rollback carries no data risk. The one thing to check after a bump is that the new tag still carries -full, and that /parsers still lists what the consumers rely on.

CI

ci/apache-tika/test-values.yaml is literally {} — no secrets, no ingress, no persistence — and there is no fixtures.yaml. The install-test waits for the Deployment to roll out; since the probes are the server's own port, "Ready" means the server answered. See CI/CD.

See also

  • Chart style guide — the rules this chart implements
  • Versioning — what a bump of 10.0.1+up3.3.1.0.full means, and why the +up suffix normalizes the dash
  • gotenberg — the second half of the paperless-ngx document pipeline
  • Chart index