Table of Contents
- apache-tika
- Prerequisites
- Installation
- Configurable values
- Operating notes
- No authentication — and it parses whatever it is sent
- The image tag has to keep its -full suffix
- The JVM only gets a quarter of the memory limit
- An empty result is a parser question, not a server question
- This chart has no Ingress, and its consumers are other namespaces
- The probed port is open inside the cluster by default
- The policy is only a boundary where the CNI enforces one
- Upgrades and backup
- CI
- See also
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.fullmeans, and why the+upsuffix normalizes the dash - gotenberg — the second half of the paperless-ngx document pipeline
- Chart index
helm-charts wiki
How we work
How it is built
How charts are written
Charts
- apache-tika
- bentopdf
- bulwark
- calibre-web-automated
- convertx
- cyberchef
- freesailarr
- garage
- gotenberg
- homeassistant
- keycloak
- matrix-synapse
- minecraft-router
- minecraft-server
- outline
- paperless-ngx
- patchmon
- rallly
- rss-bridge
- samba
- satisfactory
- searxng
- stalwart
- sure
- teamspeak-server
- template-chart
- urlaubsverwaltung
- valkey
- voucher-vault
- ytdlp-web-player
- zipline