DORA ICT risk requirements translated into engineering tasks
A practical guide for software engineers turning DORA ICT risk obligations into backlog items, configs, tests, and runbooks. It maps the regulation to concrete controls around asset inventory, logging, backups, incident handling, change management, and third-party dependencies so you can decide what to build now.
TL;DR — DORA does not ask engineers to "be compliant" in the abstract; it asks your firm to control ICT risk in ways that become very concrete engineering work: inventory systems, lock down privileged access, log security-relevant events, test backups and restores, classify incidents, and know your critical third parties. The most likely useful move is to turn each requirement into a verifiable control with an owner, an automated check, and evidence you can produce on demand. Reading time: ~7 min
What it is and where it sits
DORA's ICT risk requirements sit above your codebase but below policy-only governance. For a developer, the useful translation is: these are non-functional requirements that must exist in architecture, CI/CD, runtime configuration, observability, backup strategy, and vendor management.
In a typical stack, DORA-related controls touch:
- source control and CI/CD: branch protection, deployment approvals, artifact provenance
- infrastructure: network segmentation, IAM, secrets handling, patching
- application runtime: authn/authz, audit logs, rate limits, secure defaults
- data layer: backup schedules, restore drills, retention, encryption
- operations: alerting, incident classification, evidence retention
- third-party services: dependency inventory, critical supplier register, exit plan
What it replaces is the old pattern of scattered "security best practices" with no traceability. Under DORA, you need a chain from requirement -> implemented control -> test/evidence -> owner.
A useful mental model is that DORA wraps your normal request/data flow with control points.
View as text
[Developer] ---- 1. git push ----> [CI/CD] [CI/CD] ---- 2. build, test, sign ----> [Artifact Repo] [CI/CD] ---- 3. deploy ----> [App] [User] ---- 4. request ----> [App] [App] ---- 5. read/write ----> [Database] [App] ---- 6. call API ----> [Third-Party Service] [App] | 7. audit/security logs v [Log Store/SIEM] [Database] | 8. backup/restore tests v [Backup Storage] [Ops] | 9. alerts/incidents/runbooks v [Ticketing/On-call]
The architecture context matters because DORA questions are rarely "is TLS enabled?" and more often:
- Which systems are critical or important functions?
- Who can deploy to them?
- Can you reconstruct what happened during an incident?
- Can you restore data and service within your stated recovery targets?
- Which external provider failure would break the service, and what is your fallback?
If you cannot point to the repo, pipeline, config file, dashboard query, and restore log, you do not really have the control.
How it actually works
Take one realistic example: a payment-processing API used by an EU financial entity. You run a containerized app behind nginx, store transactions in Postgres, and depend on an external KYC API.
Step 1: classify the service and register assets
Create an asset record for the API, database, queue, backup bucket, CI pipeline, and KYC dependency. The engineering task is not a spreadsheet alone; it is tagging and discoverability.
Examples of concrete tasks:
- add infrastructure tags like
service=payments-api,data_classification=confidential,criticality=high - add
CODEOWNERS - record runtime endpoints and on-call ownership in the repo
- export a machine-readable service catalog entry
If your infra is Terraform, this usually means labels/tags in every module and a generated inventory artifact in CI.
Step 2: restrict privileged changes
DORA cares that changes to critical systems are governed. For engineering, that means branch protection, deployment separation, and auditable approvals.
Concrete implementation:
- require pull requests on
main - require at least one reviewer who is not the author
- block force-pushes
- require CI status checks before merge
- require deployments to production from CI, not from a developer laptop
- log who approved and who deployed
If your deployment still happens via ssh prod && docker compose pull && up -d, that is a control gap even if the service works perfectly.
Step 3: emit security-relevant logs with correlation IDs
At runtime, log events that matter for incident reconstruction:
- auth success/failure
- privilege changes
- config changes
- failed calls to critical third parties
- backup start/finish/failure
- admin actions
- data export events
Do not log secrets, tokens, or full PAN-like data. Log enough to answer: who did what, when, from where, against which resource, and what happened.
Step 4: back up and prove restore works
A backup job alone is not evidence. DORA-style scrutiny lands on restore testing.
For Postgres, you want:
- scheduled logical or physical backups
- encryption at rest in backup storage
- retention policy
- restore drill into an isolated environment
- measured RTO/RPO from the drill
A restore test should produce artifacts: command transcript, timestamps, row-count or checksum validation, and a ticket/runbook entry.
Step 5: monitor incidents and classify them
When the KYC API starts timing out, your app should emit errors, your alerting should fire, and your incident process should classify severity and impact. The engineering part is instrumentation and alert definitions, not just an incident policy PDF.
For example, if 5xx rate exceeds threshold or outbound dependency latency spikes, create an alert with labels for service, environment, and dependency.
Step 6: keep evidence
This is the part teams skip. For each control, keep evidence where an auditor or internal risk team can retrieve it quickly:
- CI logs showing tests and approvals
- immutable deployment records
- backup job logs and restore drill outputs
- access review exports
- incident timeline and postmortem
- dependency register with contract owner and fallback notes
When to use it (and when not to)
If your organization falls under DORA directly, you use this translation immediately. If you are a software supplier to a regulated financial entity, you probably still need most of it because customer due diligence will ask for the same evidence.
| Scenario | Recommendation |
|---|---|
| You build or operate systems for an EU financial entity's critical or important function | Treat DORA controls as backlog items with owners and evidence from day one |
| You are a SaaS vendor selling into regulated finance | Implement the same controls even if DORA does not apply to you directly; procurement will ask for them |
| Internal tool with no production data and no operational impact | Apply a lighter version: inventory, access control, backups if data matters, basic logging |
| Prototype or throwaway spike environment | You probably don't need full control coverage, but do not connect it to production data or production credentials |
| Single-tenant on-prem install fully operated by the customer | Split responsibilities explicitly; document which controls are yours vs theirs |
You probably don't need the full machinery if the system is genuinely non-critical, isolated from regulated operations, contains no sensitive data, and can be rebuilt without business impact. Even then, basic access control, patching, and backup hygiene are still cheaper than incident cleanup.
Trade-offs
Every DORA-friendly control buys risk reduction and costs engineering time or operational friction.
- Asset inventory gives you visibility and scoping; it costs tagging discipline and ongoing maintenance when systems change.
- Strong change control reduces unauthorized or risky production changes; it costs deployment speed and can frustrate small teams used to direct access.
- Detailed audit logging improves incident reconstruction; it costs storage, query spend, and careful redaction work to avoid leaking sensitive data into logs.
- Frequent backups and restore drills reduce recovery risk; they cost compute, storage, and planned operational time. Restore tests can also hit licensing or environment-capacity limits.
- Dependency registers and fallback plans reduce third-party concentration risk; they cost vendor-management overhead and sometimes force less convenient architecture choices.
- Segregation of duties helps governance; it costs flexibility in very small teams where one person currently does everything.
The common failure mode is implementing the benefit but not paying the operational cost. Example: enabling verbose logs without retention rules creates a new data-handling problem. Or adding backups without restore drills creates false confidence.
In practice
Example 1: Postgres backup and restore drill
⚠️ Restores can overwrite data or point applications at stale data if you run them in the wrong environment. Run restore drills into an isolated database name or isolated host, and block application traffic to that target.
#!/usr/bin/env bash
set -euo pipefail
TS=$(date -u +%Y%m%dT%H%M%SZ)
BACKUP_FILE="/var/backups/payments-${TS}.dump"
export PGPASSWORD="$PGPASSWORD"
pg_dump \
--format=custom \
--compress=9 \
--no-owner \
--no-privileges \
--dbname="postgresql://backup_user@db-prod:5432/payments" \
--file="$BACKUP_FILE"
sha256sum "$BACKUP_FILE" > "${BACKUP_FILE}.sha256"
aws s3 cp "$BACKUP_FILE" "s3://org-prod-db-backups/payments/"
aws s3 cp "${BACKUP_FILE}.sha256" "s3://org-prod-db-backups/payments/"
This creates a compressed logical backup, hashes it, and uploads both files. The gotcha: pg_dump --format=custom is great for selective restore, but for very large databases physical backups may be faster and more consistent for point-in-time recovery.
createdb -h db-restore-drill -U restore_user payments_restore_20261030
pg_restore \
--clean \
--if-exists \
--no-owner \
--no-privileges \
--jobs=4 \
--dbname="postgresql://restore_user@db-restore-drill:5432/payments_restore_20261030" \
/tmp/payments-20261030T020000Z.dump
psql -h db-restore-drill -U restore_user -d payments_restore_20261030 -c "select count(*) from transactions;"
This restores into an isolated database and runs a simple validation query. Watch for pg_restore: error: could not execute query: ERROR: must be owner of extension ...; --no-owner --no-privileges helps, but extensions and role-dependent objects still need planning in the target environment.
Example 2: nginx access logging with request IDs and upstream timing
log_format dora_audit '$remote_addr - $remote_user [$time_iso8601] '
'"$request" $status $body_bytes_sent '
'rt=$request_time urt=$upstream_response_time '
'req_id=$request_id upstream=$upstream_addr '
'ref="$http_referer" ua="$http_user_agent"';
map $http_x_request_id $req_id_in {
default $http_x_request_id;
"" $request_id;
}
server {
listen 443 ssl http2;
server_name api.example.com;
access_log /var/log/nginx/payments_access.log dora_audit;
location / {
proxy_set_header X-Request-ID $req_id_in;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://payments_app;
}
}
This logs request IDs and upstream timing so you can correlate app logs with edge traffic and dependency latency. The gotcha: if your app also generates request IDs, pick one source of truth or explicitly preserve inbound X-Request-ID; otherwise incident timelines become messy.
A realistic log line shape looks like:
203.0.113.24 - - [2026-10-30T09:14:22+00:00] "POST /v1/payments HTTP/1.1" 502 157 rt=3.201 urt=3.198 req_id=2f7a4d6a8c8d4d7f9b0c upstream=10.0.4.12:8080 ref="-" ua="curl/8.5.0"
That is enough to tie together client impact, exact time, upstream target, and latency during an incident involving a failing dependency.
Example 3: CI gate for change control evidence
name: build-test-deploy
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
- run: npm audit --audit-level=high
deploy-prod:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- run: ./scripts/deploy.sh
This enforces test and vulnerability gates before production deployment and leaves an auditable CI trail. The gotcha: npm audit is noisy for some dependency trees; use it as one signal, not your only supply-chain control, and define when exceptions are approved and recorded.
Further reading
- Digital Operational Resilience Act (DORA) final text
- European Supervisory Authorities guidance on major ICT-related incident reporting
- NIST SP 800-61 Computer Security Incident Handling Guide
- PostgreSQL Documentation: Backup and Restore
- OWASP Logging Cheat Sheet
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI