# File uploads: a second Ingress on the same host, for one path. # # A SEPARATE OBJECT, NOT A FOURTH PATH IN 06-ingress.yaml. ingress-nginx applies an Ingress's # annotations to every path that object declares, so the only way to give /api/uploads/ a larger # body ceiling without handing the same ceiling to every API route is an object of its own. The # controller merges both Ingresses into one server for the host and picks the longest matching # prefix per request, so /api/uploads/ lands here and everything else under /api/ keeps the 4m of # the main Ingress. # # PATCH ITS HOST LIKE THE MAIN ONE. An overlay that rewrites `jarvis.example.com` in 06-ingress.yaml # must rewrite it here too, TLS block included. Left unpatched, this rule serves a host nobody # visits and uploads quietly fall back to the /api/ rule — where every upload between 4 and 13 MB # meets the controller's HTML 413 instead of Jarvis's own answer, and small files keep working, so # nothing looks broken until somebody attaches a large one. apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: jarvis-uploads namespace: jarvis annotations: # With the main Ingress, for the same reason: last, once the Services have endpoints. argocd.argoproj.io/sync-wave: "2" # 13376k is 13 MiB and 64 KiB: exactly the most the API itself reads on this path — a workspace # image of up to 10 MiB, the copy of it prepared for the model (at most 3 MiB), and 64 KiB for # the multipart framing and the name (`BODY_MAX_BYTES.documents` + `MULTIPART_OVERHEAD`, in # apps/api/src/files/upload-gate.interceptor.ts; both refuse strictly above). Not less: a body # the API would take would meet this ceiling first, and the controller's answer is an HTML page # the client cannot translate. Not more: the API refuses anything larger unread. A composer # attachment over 10 MiB still gets through here, and meets the API's own JSON 413. nginx.ingress.kubernetes.io/proxy-body-size: "13376k" # Everything the API does with a file happens before it answers: a wait for an upload slot, a # PDF's text extraction (capped at 20s), a 13 MB row write. Two minutes is headroom over that, # and far below the hour the main Ingress keeps open for idle websockets, which have no business # on this path. nginx.ingress.kubernetes.io/proxy-read-timeout: "120" nginx.ingress.kubernetes.io/proxy-send-timeout: "120" # Request buffering is left at the controller's default, ON: the controller reads the whole body # before it opens a connection to the api, so a slow client costs the controller's disk rather # than one of the api pod's few upload slots and the memory behind it. # # And no `custom-http-errors` override. A controller that sets it replaces the body of the # statuses it lists, and every refusal the api makes on this path is a 4xx — busy included, # which is a 429, never a 503. The one 4xx such lists usually name, 404, is a status whose body # the client never reads. spec: ingressClassName: nginx tls: - hosts: ["jarvis.example.com"] secretName: jarvis-tls rules: - host: jarvis.example.com http: paths: - path: /api/uploads/ pathType: Prefix backend: service: name: jarvis-api port: name: http