This document describes the comprehensive versioning and rollback system for Docker Compose configurations implemented in the selfhostly platform.
- Initial Version: When an app is created, version 1 is automatically saved
- Change Tracking: Every compose file update creates a new version
- Version Metadata: Each version tracks:
- Version number (sequential integer)
- Compose file content
- Change reason (optional description)
- Changed by (username of authenticated user)
- Creation timestamp
- Rollback information (if version was created from a rollback)
- Current status (whether this is the active version)
- Timeline View: Visual timeline of all versions in reverse chronological order
- Version Details: Each version displays:
- Version number with visual indicator
- Current version badge
- Rollback indicator (if applicable)
- Change reason/description
- Timestamp with relative time (e.g., "2h ago")
- Changed by username
- Actions:
- View version content
- Rollback to any previous version
- Safe Rollback: Creates a new version with previous content (non-destructive)
- Rollback Metadata: Tracks which version was rolled back from
- Container Update: After rollback, prompts to update containers
- History Preserved: All versions remain available for future reference
CREATE TABLE compose_versions (
id TEXT PRIMARY KEY,
app_id TEXT NOT NULL,
version INTEGER NOT NULL,
compose_content TEXT NOT NULL,
change_reason TEXT,
changed_by TEXT,
is_current INTEGER NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
rolled_back_from INTEGER,
UNIQUE(app_id, version),
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE
);
CREATE INDEX idx_compose_versions_app_id ON compose_versions(app_id);
CREATE INDEX idx_compose_versions_is_current ON compose_versions(app_id, is_current);type ComposeVersion struct {
ID string `json:"id" db:"id"`
AppID string `json:"app_id" db:"app_id"`
Version int `json:"version" db:"version"`
ComposeContent string `json:"compose_content" db:"compose_content"`
ChangeReason *string `json:"change_reason" db:"change_reason"`
ChangedBy *string `json:"changed_by" db:"changed_by"`
IsCurrent bool `json:"is_current" db:"is_current"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
RolledBackFrom *int `json:"rolled_back_from" db:"rolled_back_from"`
}CreateComposeVersion()- Create a new versionGetComposeVersionsByAppID()- Get all versions for an appGetComposeVersion()- Get a specific versionGetCurrentComposeVersion()- Get the active versionGetLatestVersionNumber()- Get the latest version numberMarkVersionAsCurrent()- Set a version as currentMarkAllVersionsAsNotCurrent()- Clear current flag from all versions
GET /api/apps/:id/compose/versions- List all versionsGET /api/apps/:id/compose/versions/:version- Get specific versionPOST /api/apps/:id/compose/rollback/:version- Rollback to version
On App Creation:
// Create initial compose version (version 1)
initialReason := "Initial version"
initialVersion := db.NewComposeVersion(app.ID, 1, app.ComposeContent, &initialReason, changedBy)
db.CreateComposeVersion(initialVersion)On App Update:
// Check if compose content has changed
composeChanged := composeContent != app.ComposeContent
if composeChanged {
latestVersion, _ := db.GetLatestVersionNumber(id)
db.MarkAllVersionsAsNotCurrent(id)
updateReason := "Compose file updated"
newVersion := db.NewComposeVersion(id, latestVersion+1, app.ComposeContent, &updateReason, changedBy)
db.CreateComposeVersion(newVersion)
}On Rollback:
// Create new version with rolled-back content
newVersionNumber := currentVersionNumber + 1
changeReason := fmt.Sprintf("Rolled back to version %d", targetVersion)
newVersion := db.NewComposeVersion(id, newVersionNumber, targetComposeVersion.ComposeContent, &changeReason, changedBy)
newVersion.RolledBackFrom = &targetVersion
db.MarkAllVersionsAsNotCurrent(id)
db.CreateComposeVersion(newVersion)
db.UpdateApp(app) // Update app with rolled-back content
dockerManager.WriteComposeFile(app.Name, app.ComposeContent) // Update file on diskexport interface ComposeVersion {
id: string;
app_id: string;
version: number;
compose_content: string;
change_reason?: string | null;
changed_by?: string | null;
is_current: boolean;
created_at: string;
rolled_back_from?: number | null;
}useComposeVersions(appId)- Fetch all versionsuseComposeVersion(appId, version)- Fetch specific versionuseRollbackToVersion(appId)- Rollback mutation
ComposeVersionHistory Component (web/src/features/app-details/components/ComposeVersionHistory.tsx)
- Displays version timeline
- Shows version metadata
- Handles rollback confirmation
- Responsive design (mobile + desktop)
ComposeEditor Component (web/src/features/app-details/components/ComposeEditor.tsx)
- Integrated version history sidebar
- Auto-updates on rollback
- Mobile-friendly toggle for version history
- Side-by-side layout on desktop
- User creates app with initial compose file
- System automatically creates version 1
- Version is marked as current
- User edits compose content in editor
- User clicks "Save Changes"
- System detects changes and creates new version
- New version marked as current
- Previous version remains in history
- User views version history
- User clicks "Rollback" on desired version
- System shows confirmation dialog
- On confirm:
- Creates new version with old content
- Marks rollback metadata
- Updates app compose content
- Updates compose file on disk
- User prompted to update containers
- Desktop: Version history shown in sidebar
- Mobile: Toggleable version history panel
- Timeline shows:
- All versions newest to oldest
- Visual indicators for current version
- Rollback badges
- Timestamps and authors
- Safety: Never lose a working configuration
- Confidence: Experiment knowing you can rollback
- Traceability: See who changed what and when
- Auditability: Complete history of all changes
- Non-destructive: All operations preserve history
- Cascading Deletes: Versions deleted when app deleted
- Performance: Indexed queries for fast retrieval
- Storage: Minimal overhead (text compression possible)
- Keep descriptive change reasons
- Review version history before making major changes
- Test changes before rolling back in production
- Remember to update containers after rollback
- Versions automatically cleaned up when app deleted
- Consider periodic archival for very old versions
- Monitor database size if many versions accumulate
Potential improvements to consider:
- Diff Viewer: Show differences between versions
- Version Tags: Mark important versions (e.g., "stable", "production")
- Version Notes: Add detailed notes to versions
- Scheduled Rollbacks: Schedule rollback for specific time
- Version Comparison: Compare any two versions side-by-side
- Auto-save: Create versions on timer or after N edits
- Version Limits: Configure max versions per app
- Compression: Compress old version content
- Export/Import: Export version history with app
- Merge Conflicts: Handle concurrent edits
GET /api/apps/{appId}/compose/versions
Returns array of all versions for the app, ordered by version DESC.
GET /api/apps/{appId}/compose/versions/{version}
Returns the specified version details.
POST /api/apps/{appId}/compose/rollback/{version}
Content-Type: application/json
{
"change_reason": "Optional reason for rollback"
}
Creates new version with content from target version.
- User authentication required for all operations
- User name captured for audit trail
- Versions tied to app via foreign key
- Cascading delete ensures no orphaned versions
- SQL injection prevented via parameterized queries
- Version numbers start at 1 and increment
- Rollback creates new version, doesn't modify old ones
- Current version tracked via
is_currentflag - Only one version can be current at a time
- Database transaction ensures atomic current version updates
- Compose file on disk always matches current version
- Frontend auto-refreshes after rollback
- Create app creates version 1
- Update compose creates new version
- No version created if content unchanged
- Rollback creates new version with old content
- Rollback metadata correctly set
- Current version flag properly managed
- Version history displays correctly
- Mobile version history toggle works
- User names captured when authenticated
- Timestamps accurate
- Cascade delete removes versions
- Concurrent edits handled safely
- Check if compose content actually changed
- Verify database connection
- Check logs for errors
- Ensure target version exists
- Check app permissions
- Verify disk space for file write
- Check API endpoint accessibility
- Verify authentication
- Check browser console for errors
This versioning system provides a robust, production-ready solution for managing Docker Compose configurations with full history tracking and safe rollback capabilities. The implementation follows best practices for data integrity, user experience, and system maintainability.