System Architecture
Three Docker containers. One exposed port. Here's exactly what's running inside your server after docker compose up.
Docker Volumes
postgres_dataPostgreSQL data directory. Your encrypted audit logs live here. Persists across container restarts and updates.
server_dataApp config (config.json), WAL crash-recovery files (queue.wal). Contains your Fernet encryption key — back this up.
secretsdb_password file. Shared between tampertrail-server and tampertrail-db. Never exposed outside Docker network.
Key Design Decisions
Intentional. Hash chain integrity requires a strict sequential write order. Multiple workers would need distributed locking — adding latency with no throughput benefit, since the bottleneck is disk I/O, not CPU.
Every incoming log is written to queue.wal on disk before PostgreSQL receives it. If the process crashes mid-batch, the WAL is replayed on restart. Zero data loss by design — same pattern PostgreSQL itself uses.
The metadata field is Fernet-encrypted before the database driver is ever called. PostgreSQL never sees the plaintext — even with direct database read access, an attacker sees only binary ciphertext.
FastAPI and PostgreSQL bind exclusively to the internal Docker bridge network. They are unreachable from outside the host machine without explicitly publishing their ports — which TamperTrail's compose file does not do.
FORCE ROW LEVEL SECURITY is enabled on audit_logs and chain_checkpoints. Even if the application-level tenant_id filter has a bug, the database returns zero rows instead of leaking cross-tenant data.