This document records key implementation decisions made during the design and development of this containerized Oxidized deployment.
This document serves as a decision log to:
- Explain why specific approaches were chosen
- Document alternatives considered
- Provide context for future maintainers
- Justify deviations from common practices
Date: 2026-01-17
Decision: Use Podman Quadlets (.container files) instead of traditional systemd unit files
Context:
- Need systemd integration for automatic startup on boot
- Want declarative container configuration
- Require reliable restart policies
Alternatives Considered:
-
Manual
podman runcommands- ❌ Not persistent across reboots
- ❌ Configuration not tracked
- ❌ Difficult to manage
-
Traditional systemd unit file with ExecStart
- ✅ Works reliably
- ❌ Verbose and error-prone
- ❌ Hard to maintain long container configurations
-
Podman Quadlets (CHOSEN)
- ✅ Declarative configuration
- ✅ Automatic systemd integration
- ✅ Clean, readable syntax
- ✅ Version-controllable
- ✅ Systemd native (247+)
Rationale: Quadlets provide the best balance of simplicity, maintainability, and systemd integration. They generate proper systemd units automatically and are the recommended approach for RHEL 9/10.
References:
- Podman Quadlet Documentation
- RHEL 10 systemd version: 252+ (supports Quadlets)
Date: 2026-01-17
Decision: Use regular Git repository at /srv/oxidized/git/configs.git
Context:
- Need to store device configurations in Git
- Want to inspect files easily
- Single-server deployment (no push/pull initially)
Alternatives Considered:
-
Bare Git repository (
.gitonly, no working tree)- ✅ Traditional for Git output
- ✅ Required for remote push
- ❌ Can't easily view files on disk
- ❌ Requires Git commands to inspect content
-
Regular Git repository (CHOSEN)
- ✅ Working directory with actual files
- ✅ Easy to inspect:
ls /srv/oxidized/git/configs.git/ - ✅ Simpler for beginners
- ✅ No loss of functionality for single-server
⚠️ Requires conversion if adding remote push later
Rationale: For a single-server deployment without remote Git integration, a regular repository is more user-friendly. Files can be directly inspected, compared, and accessed without Git commands. If remote push is needed later, the repository can be converted to bare.
Migration Path (if needed in future):
cd /srv/oxidized/git
git clone --bare configs.git configs-bare.git
mv configs.git configs.git.old
mv configs-bare.git configs.gitDate: 2026-01-17
Decision: Use /srv/oxidized for host persistent storage
Context:
- Need persistent storage on host
- SELinux enforcing
- Standard filesystem hierarchy
Alternatives Considered:
-
/var/lib/oxidized- ✅ Standard for application state
- ❌ Mixed with other
/var/libcontent ⚠️ May conflict with native package
-
/opt/oxidized- ✅ For third-party software
- ❌ Historically for pre-compiled software
- ❌ Less standard for data
-
/srv/oxidized(CHOSEN)- ✅ Designed for site-specific data
- ✅ Clear separation from system paths
- ✅ Easier to backup (isolated)
- ✅ No conflicts with potential native packages
- ✅ Commonly used for containerized services
Rationale:
/srv is specifically intended for "data for services provided by the system" per FHS.
This makes it ideal for containerized service data.
It's also easier to manage, backup, and doesn't conflict with system paths.
References:
Date: 2026-01-17
Decision: Use :Z (exclusive) for all volume mounts
Context:
- SELinux enforcing mode required
- Container needs read/write access
- Single container accessing these volumes
Alternatives Considered:
-
:z(shared)- ✅ Allows multiple containers to share volume
- ❌ Unnecessary for single-container deployment
- ❌ Less restrictive security
-
:Z(exclusive) (CHOSEN)- ✅ Exclusive access to container
- ✅ More restrictive (better security)
- ✅ Appropriate for single-container use case
- ✅ Automatic SELinux relabeling
-
Manual
chconorsemanage- ❌ Not idempotent
- ❌ More complex
- ❌ Violates "boring solutions" principle
- ❌
chconchanges are not persistent across relabeling
Rationale:
Since only one container (Oxidized) accesses these volumes, :Z provides appropriate isolation
and automatic SELinux context management without requiring manual commands.
References:
- Podman SELinux Documentation
- CONTEXT.md: "Adhere to SELinux best practices (semanage over chcon)"
Date: 2026-01-17
Decision: Logs written to /var/lib/oxidized/logs (container path)
Context:
- Container environment
- Persistent log storage needed
- Logrotate on host
Alternatives Considered:
-
Container
/var/log- ❌ Lost when container recreated
- ❌ Not accessible on host
-
Host
/var/log/oxidized- ✅ Standard location
- ❌ Requires additional volume mount
- ❌ Conflicts with container's internal logging
⚠️ Requirements explicitly state: "No logs written to/var/log"
-
/var/lib/oxidized/logs(CHOSEN)- ✅ Persistent across container recreations
- ✅ Part of application state
- ✅ Accessible on host at
/srv/oxidized/logs - ✅ Complies with requirements
- ✅ Logrotate handles via copytruncate
Rationale:
Keeping logs within the application state directory (/var/lib/oxidized) ensures persistence
and aligns with containerized application best practices.
The host-side logrotate uses copytruncate to handle the open file.
References:
- docs/requirements.md: "No logs written to
/var/log"
Date: 2026-01-22
Decision: Pin to oxidized/oxidized:0.35.0 (stable release)
Context:
- Production deployment
- Need stability and predictability
- Must avoid breaking changes
Alternatives Considered:
-
latesttag- ❌ Unpredictable changes
- ❌ Can break production
- ❌ Violates requirements
- ❌ No rollback path
-
nightlyormastertags- ❌ Development/unstable
- ❌ Not for production
-
Specific version tag
0.35.0(CHOSEN)- ✅ Stable, tested release
- ✅ Predictable behavior
- ✅ Controlled upgrades
- ✅ Easy rollback
- ✅ Documented upgrade path
Rationale:
Version pinning is essential for production stability. 0.35.0 is the latest stable release (updated 2025-12-04).
Upgrades are manual and deliberate, following the process in UPGRADE.md.
Note: When implementing this, verify the latest stable version on Docker Hub and update accordingly.
Date: 2026-01-17
Decision: Use rootful Podman (run as root)
Context:
- Need to bind to port 8888
- Systemd integration required
- File permissions management
Alternatives Considered:
- ✅ Better security isolation
- ❌ Complications with port binding < 1024 (not applicable here)
- ❌ User-level systemd units more complex
- ❌ File permission complexity with host mounts
⚠️ More difficult to manage in multi-admin environment
- ✅ Simple systemd integration
- ✅ Straightforward file permissions
- ✅ System-wide service
- ✅ Standard for production services
- ✅ Container still runs as non-root inside (UID 30000)
Rationale: Rootful Podman provides simpler management for a system-wide production service. The container itself runs as non-root (UID 30000), providing defense-in-depth. This is the standard approach for production services on RHEL.
Security Note: The container process runs as UID 30000 inside the container (User=30000:30000 in Quadlet), providing process isolation even though Podman runs as root.
Date: 2026-01-17
Decision: Run container process as UID 30000
Context:
- Oxidized default user UID
- Security best practice
- SELinux compatibility
Alternatives Considered:
- ❌ Security risk
- ❌ Not necessary
- ❌ Violates least privilege
- ✅ Oxidized image default
- ✅ Non-root process
- ✅ Works with SELinux
:Z - ✅ Least privilege
Rationale: The official Oxidized image is designed to run as UID 30000. Using this default ensures compatibility and security. SELinux handles access control via context, making the specific UID less critical.
Date: 2026-01-17
Decision: Set default polling interval to 3600 seconds (1 hour)
Context:
- Network device configuration backup
- ~100 devices expected
- Balance between freshness and load
Alternatives Considered:
| Interval | Pros | Cons | Use Case |
|---|---|---|---|
| 15 min | Faster detection of changes | High load, more Git commits | Critical infrastructure |
| 1 hour | Balanced | Good freshness | General production ✅ |
| 4 hours | Lower load | Slower change detection | Stable networks |
| Daily | Minimal load | Stale data risk | Non-critical devices |
Rationale: Hourly polling provides a good balance:
- Changes detected within reasonable time
- Manageable load on network devices
- Reasonable Git commit frequency
- Aligns with typical change windows
Note: Interval is configurable in /srv/oxidized/config/config. Adjust based on change frequency and device count.
Date: 2026-01-17
Decision: Use copytruncate in logrotate configuration
Context:
- Container keeps log file handle open
- Can't signal container to reopen logs
- Need reliable rotation
Alternatives Considered:
- ❌ Requires signaling application
- ❌ Complex with containers
- ❌ May lose log entries
- ✅ Works with open file handles
- ✅ No signaling required
- ✅ Container-friendly
⚠️ Brief potential for log loss during copy (acceptable risk)
Rationale:
copytruncate is the standard approach for containerized applications where you cannot easily signal
the process to reopen log files. The tiny window of potential log loss is acceptable for configuration backup logs.
Date: 2026-01-17
Decision: Default to plaintext credentials in config file with documentation for alternatives
Context:
- Need device authentication
- Balance simplicity vs security
- Different org requirements
Alternatives Considered:
- ✅ Simple to implement
- ✅ Works out of box
⚠️ File must be protected (permissions)⚠️ Not suitable for high-security environments
- ✅ Better security
- ❌ Requires Quadlet modification
- ❌ More complex for users
- ✅ Best security
- ❌ Complex setup
- ❌ External dependencies
- ❌ Out of scope
Rationale: Start with the simplest working solution (plaintext in protected file) with clear documentation about security implications and alternatives. Users can upgrade to environment variables or secrets managers as needed.
Security Notes:
- Config file permissions: 644 (readable by container)
- Located in
/srv/oxidized/config(root-owned) - NOT committed to Git (if users fork this repo)
- Document environment variable option in config file
Date: 2026-01-17
Decision: Use local Git repository only, no remote integration
Context:
- Simplify initial deployment
- Avoid external dependencies
- Requirements explicitly state "local Git only"
Alternatives Considered:
- ❌ Requires additional setup
- ❌ Credentials management
- ❌ Network dependencies
- ❌ Out of scope per requirements
- ✅ Simple, reliable
- ✅ No external dependencies
- ✅ Works offline
- ✅ Can be added later if needed
Rationale: Local Git provides version control and diff capabilities without external dependencies. Remote push can be added later as an enhancement if needed.
Future Enhancement: If remote push is needed, Oxidized supports Git hooks and remote configuration. See Oxidized Git Hooks Documentation.
These decisions should be reviewed:
- Annually: Check if Oxidized best practices have changed
- On major version upgrade: Validate decisions still apply
- When requirements change: Re-evaluate if decisions still meet needs
- CONTEXT.md - AI Engineering Standards
- requirements.md - Project Requirements
- Podman Documentation
- Oxidized Documentation
- RHEL 10 Documentation
When adding new decisions, use this format:
### Decision N: [Title]
**Date**: YYYY-MM-DD
**Decision**: [What was decided]
**Context**: [Why this decision was needed]
**Alternatives Considered**:
1. Option A
- ✅ Pro
- ❌ Con
2. Option B (CHOSEN)
- ✅ Pros
- ❌ Cons
Rationale: [Why this option was chosen]
**References**: [Links to relevant docs]Last Updated: 2026-01-17