diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden
index 1444f80961..c83d3aa273 100644
--- a/cli/testdata/coder_server_--help.golden
+++ b/cli/testdata/coder_server_--help.golden
@@ -696,6 +696,27 @@ updating, and deleting workspace resources.
Number of provisioner daemons to create on start. If builds are stuck
in queued state for a long time, consider increasing this.
+RETENTION OPTIONS:
+Configure data retention policies for various database tables. Retention
+policies automatically purge old data to reduce database size and improve
+performance. Setting a retention duration to 0 disables automatic purging for
+that data type.
+
+ --api-keys-retention duration, $CODER_API_KEYS_RETENTION (default: 7d)
+ How long expired API keys are retained before being deleted. Keeping
+ expired keys allows the backend to return a more helpful error when a
+ user tries to use an expired key. Set to 0 to disable automatic
+ deletion of expired keys.
+
+ --audit-logs-retention duration, $CODER_AUDIT_LOGS_RETENTION (default: 0)
+ How long audit log entries are retained. Set to 0 to disable (keep
+ indefinitely). We advise keeping audit logs for at least a year, and
+ in accordance with your compliance requirements.
+
+ --connection-logs-retention duration, $CODER_CONNECTION_LOGS_RETENTION (default: 0)
+ How long connection log entries are retained. Set to 0 to disable
+ (keep indefinitely).
+
TELEMETRY OPTIONS:
Telemetry is critical to our ability to improve Coder. We strip all personal
information before sending data to our servers. Please only disable telemetry
diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden
index 8b254fe295..36ab491274 100644
--- a/cli/testdata/server-config.yaml.golden
+++ b/cli/testdata/server-config.yaml.golden
@@ -742,3 +742,22 @@ aibridge:
# (token, prompt, tool use).
# (default: 60d, type: duration)
retention: 1440h0m0s
+# Configure data retention policies for various database tables. Retention
+# policies automatically purge old data to reduce database size and improve
+# performance. Setting a retention duration to 0 disables automatic purging for
+# that data type.
+retention:
+ # How long audit log entries are retained. Set to 0 to disable (keep
+ # indefinitely). We advise keeping audit logs for at least a year, and in
+ # accordance with your compliance requirements.
+ # (default: 0, type: duration)
+ audit_logs: 0s
+ # How long connection log entries are retained. Set to 0 to disable (keep
+ # indefinitely).
+ # (default: 0, type: duration)
+ connection_logs: 0s
+ # How long expired API keys are retained before being deleted. Keeping expired
+ # keys allows the backend to return a more helpful error when a user tries to use
+ # an expired key. Set to 0 to disable automatic deletion of expired keys.
+ # (default: 7d, type: duration)
+ api_keys: 168h0m0s
diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index f80959192d..658bda7c1e 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -14302,6 +14302,9 @@ const docTemplate = `{
"redirect_to_access_url": {
"type": "boolean"
},
+ "retention": {
+ "$ref": "#/definitions/codersdk.RetentionConfig"
+ },
"scim_api_key": {
"type": "string"
},
@@ -17728,6 +17731,23 @@ const docTemplate = `{
}
}
},
+ "codersdk.RetentionConfig": {
+ "type": "object",
+ "properties": {
+ "api_keys": {
+ "description": "APIKeys controls how long expired API keys are retained before being deleted.\nKeys are only deleted if they have been expired for at least this duration.\nDefaults to 7 days to preserve existing behavior.",
+ "type": "integer"
+ },
+ "audit_logs": {
+ "description": "AuditLogs controls how long audit log entries are retained.\nSet to 0 to disable (keep indefinitely).",
+ "type": "integer"
+ },
+ "connection_logs": {
+ "description": "ConnectionLogs controls how long connection log entries are retained.\nSet to 0 to disable (keep indefinitely).",
+ "type": "integer"
+ }
+ }
+ },
"codersdk.Role": {
"type": "object",
"properties": {
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index 8d54e2b310..1c3976a96b 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -12886,6 +12886,9 @@
"redirect_to_access_url": {
"type": "boolean"
},
+ "retention": {
+ "$ref": "#/definitions/codersdk.RetentionConfig"
+ },
"scim_api_key": {
"type": "string"
},
@@ -16190,6 +16193,23 @@
}
}
},
+ "codersdk.RetentionConfig": {
+ "type": "object",
+ "properties": {
+ "api_keys": {
+ "description": "APIKeys controls how long expired API keys are retained before being deleted.\nKeys are only deleted if they have been expired for at least this duration.\nDefaults to 7 days to preserve existing behavior.",
+ "type": "integer"
+ },
+ "audit_logs": {
+ "description": "AuditLogs controls how long audit log entries are retained.\nSet to 0 to disable (keep indefinitely).",
+ "type": "integer"
+ },
+ "connection_logs": {
+ "description": "ConnectionLogs controls how long connection log entries are retained.\nSet to 0 to disable (keep indefinitely).",
+ "type": "integer"
+ }
+ }
+ },
"codersdk.Role": {
"type": "object",
"properties": {
diff --git a/codersdk/deployment.go b/codersdk/deployment.go
index 7b09e3a83c..464ae7ab2b 100644
--- a/codersdk/deployment.go
+++ b/codersdk/deployment.go
@@ -501,6 +501,7 @@ type DeploymentValues struct {
WebTerminalRenderer serpent.String `json:"web_terminal_renderer,omitempty" typescript:",notnull"`
AllowWorkspaceRenames serpent.Bool `json:"allow_workspace_renames,omitempty" typescript:",notnull"`
Healthcheck HealthcheckConfig `json:"healthcheck,omitempty" typescript:",notnull"`
+ Retention RetentionConfig `json:"retention,omitempty" typescript:",notnull"`
CLIUpgradeMessage serpent.String `json:"cli_upgrade_message,omitempty" typescript:",notnull"`
TermsOfServiceURL serpent.String `json:"terms_of_service_url,omitempty" typescript:",notnull"`
Notifications NotificationsConfig `json:"notifications,omitempty" typescript:",notnull"`
@@ -813,6 +814,23 @@ type HealthcheckConfig struct {
ThresholdDatabase serpent.Duration `json:"threshold_database" typescript:",notnull"`
}
+// RetentionConfig contains configuration for data retention policies.
+// These settings control how long various types of data are retained in the database
+// before being automatically purged. Setting a value to 0 disables retention for that
+// data type (data is kept indefinitely).
+type RetentionConfig struct {
+ // AuditLogs controls how long audit log entries are retained.
+ // Set to 0 to disable (keep indefinitely).
+ AuditLogs serpent.Duration `json:"audit_logs" typescript:",notnull"`
+ // ConnectionLogs controls how long connection log entries are retained.
+ // Set to 0 to disable (keep indefinitely).
+ ConnectionLogs serpent.Duration `json:"connection_logs" typescript:",notnull"`
+ // APIKeys controls how long expired API keys are retained before being deleted.
+ // Keys are only deleted if they have been expired for at least this duration.
+ // Defaults to 7 days to preserve existing behavior.
+ APIKeys serpent.Duration `json:"api_keys" typescript:",notnull"`
+}
+
type NotificationsConfig struct {
// The upper limit of attempts to send a notification.
MaxSendAttempts serpent.Int64 `json:"max_send_attempts" typescript:",notnull"`
@@ -1180,6 +1198,11 @@ func (c *DeploymentValues) Options() serpent.OptionSet {
Name: "AI Bridge",
YAML: "aibridge",
}
+ deploymentGroupRetention = serpent.Group{
+ Name: "Retention",
+ Description: "Configure data retention policies for various database tables. Retention policies automatically purge old data to reduce database size and improve performance. Setting a retention duration to 0 disables automatic purging for that data type.",
+ YAML: "retention",
+ }
)
httpAddress := serpent.Option{
@@ -3363,6 +3386,40 @@ Write out the current server config as YAML to stdout.`,
YAML: "retention",
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
},
+ // Retention settings
+ {
+ Name: "Audit Logs Retention",
+ Description: "How long audit log entries are retained. Set to 0 to disable (keep indefinitely). We advise keeping audit logs for at least a year, and in accordance with your compliance requirements.",
+ Flag: "audit-logs-retention",
+ Env: "CODER_AUDIT_LOGS_RETENTION",
+ Value: &c.Retention.AuditLogs,
+ Default: "0",
+ Group: &deploymentGroupRetention,
+ YAML: "audit_logs",
+ Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
+ },
+ {
+ Name: "Connection Logs Retention",
+ Description: "How long connection log entries are retained. Set to 0 to disable (keep indefinitely).",
+ Flag: "connection-logs-retention",
+ Env: "CODER_CONNECTION_LOGS_RETENTION",
+ Value: &c.Retention.ConnectionLogs,
+ Default: "0",
+ Group: &deploymentGroupRetention,
+ YAML: "connection_logs",
+ Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
+ },
+ {
+ Name: "API Keys Retention",
+ Description: "How long expired API keys are retained before being deleted. Keeping expired keys allows the backend to return a more helpful error when a user tries to use an expired key. Set to 0 to disable automatic deletion of expired keys.",
+ Flag: "api-keys-retention",
+ Env: "CODER_API_KEYS_RETENTION",
+ Value: &c.Retention.APIKeys,
+ Default: "7d",
+ Group: &deploymentGroupRetention,
+ YAML: "api_keys",
+ Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
+ },
{
Name: "Enable Authorization Recordings",
Description: "All api requests will have a header including all authorization calls made during the request. " +
diff --git a/codersdk/deployment_test.go b/codersdk/deployment_test.go
index 0b46320892..8b811480d5 100644
--- a/codersdk/deployment_test.go
+++ b/codersdk/deployment_test.go
@@ -703,3 +703,65 @@ func TestNotificationsCanBeDisabled(t *testing.T) {
})
}
}
+
+func TestRetentionConfigParsing(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ environment []serpent.EnvVar
+ expectedAuditLogs time.Duration
+ expectedConnectionLogs time.Duration
+ expectedAPIKeys time.Duration
+ }{
+ {
+ name: "Defaults",
+ environment: []serpent.EnvVar{},
+ expectedAuditLogs: 0,
+ expectedConnectionLogs: 0,
+ expectedAPIKeys: 7 * 24 * time.Hour, // 7 days default
+ },
+ {
+ name: "IndividualRetentionSet",
+ environment: []serpent.EnvVar{
+ {Name: "CODER_AUDIT_LOGS_RETENTION", Value: "30d"},
+ {Name: "CODER_CONNECTION_LOGS_RETENTION", Value: "60d"},
+ {Name: "CODER_API_KEYS_RETENTION", Value: "14d"},
+ },
+ expectedAuditLogs: 30 * 24 * time.Hour,
+ expectedConnectionLogs: 60 * 24 * time.Hour,
+ expectedAPIKeys: 14 * 24 * time.Hour,
+ },
+ {
+ name: "AllRetentionSet",
+ environment: []serpent.EnvVar{
+ {Name: "CODER_AUDIT_LOGS_RETENTION", Value: "365d"},
+ {Name: "CODER_CONNECTION_LOGS_RETENTION", Value: "30d"},
+ {Name: "CODER_API_KEYS_RETENTION", Value: "0"},
+ },
+ expectedAuditLogs: 365 * 24 * time.Hour,
+ expectedConnectionLogs: 30 * 24 * time.Hour,
+ expectedAPIKeys: 0, // Explicitly disabled
+ },
+ }
+
+ for _, tt := range tests {
+ tt := tt
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ dv := codersdk.DeploymentValues{}
+ opts := dv.Options()
+
+ err := opts.SetDefaults()
+ require.NoError(t, err)
+
+ err = opts.ParseEnv(tt.environment)
+ require.NoError(t, err)
+
+ assert.Equal(t, tt.expectedAuditLogs, dv.Retention.AuditLogs.Value(), "audit logs retention mismatch")
+ assert.Equal(t, tt.expectedConnectionLogs, dv.Retention.ConnectionLogs.Value(), "connection logs retention mismatch")
+ assert.Equal(t, tt.expectedAPIKeys, dv.Retention.APIKeys.Value(), "api keys retention mismatch")
+ })
+ }
+}
diff --git a/docs/reference/api/general.md b/docs/reference/api/general.md
index 2f30b4c1d6..39793443ca 100644
--- a/docs/reference/api/general.md
+++ b/docs/reference/api/general.md
@@ -463,6 +463,11 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
"disable_all": true
},
"redirect_to_access_url": true,
+ "retention": {
+ "api_keys": 0,
+ "audit_logs": 0,
+ "connection_logs": 0
+ },
"scim_api_key": "string",
"session_lifetime": {
"default_duration": 0,
diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md
index 7f88558c75..f58285a584 100644
--- a/docs/reference/api/schemas.md
+++ b/docs/reference/api/schemas.md
@@ -3147,6 +3147,11 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"disable_all": true
},
"redirect_to_access_url": true,
+ "retention": {
+ "api_keys": 0,
+ "audit_logs": 0,
+ "connection_logs": 0
+ },
"scim_api_key": "string",
"session_lifetime": {
"default_duration": 0,
@@ -3663,6 +3668,11 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
"disable_all": true
},
"redirect_to_access_url": true,
+ "retention": {
+ "api_keys": 0,
+ "audit_logs": 0,
+ "connection_logs": 0
+ },
"scim_api_key": "string",
"session_lifetime": {
"default_duration": 0,
@@ -3808,6 +3818,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
| `proxy_trusted_origins` | array of string | false | | |
| `rate_limit` | [codersdk.RateLimitConfig](#codersdkratelimitconfig) | false | | |
| `redirect_to_access_url` | boolean | false | | |
+| `retention` | [codersdk.RetentionConfig](#codersdkretentionconfig) | false | | |
| `scim_api_key` | string | false | | |
| `session_lifetime` | [codersdk.SessionLifetime](#codersdksessionlifetime) | false | | |
| `ssh_keygen_algorithm` | string | false | | |
@@ -7506,6 +7517,24 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
| `message` | string | false | | Message is an actionable message that depicts actions the request took. These messages should be fully formed sentences with proper punctuation. Examples: - "A user has been created." - "Failed to create a user." |
| `validations` | array of [codersdk.ValidationError](#codersdkvalidationerror) | false | | Validations are form field-specific friendly error messages. They will be shown on a form field in the UI. These can also be used to add additional context if there is a set of errors in the primary 'Message'. |
+## codersdk.RetentionConfig
+
+```json
+{
+ "api_keys": 0,
+ "audit_logs": 0,
+ "connection_logs": 0
+}
+```
+
+### Properties
+
+| Name | Type | Required | Restrictions | Description |
+|-------------------|---------|----------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| `api_keys` | integer | false | | Api keys controls how long expired API keys are retained before being deleted. Keys are only deleted if they have been expired for at least this duration. Defaults to 7 days to preserve existing behavior. |
+| `audit_logs` | integer | false | | Audit logs controls how long audit log entries are retained. Set to 0 to disable (keep indefinitely). |
+| `connection_logs` | integer | false | | Connection logs controls how long connection log entries are retained. Set to 0 to disable (keep indefinitely). |
+
## codersdk.Role
```json
diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md
index 8f86ea7248..a173c043b0 100644
--- a/docs/reference/cli/server.md
+++ b/docs/reference/cli/server.md
@@ -1770,3 +1770,36 @@ Whether to inject Coder's MCP tools into intercepted AI Bridge requests (require
| Default | 60d |
Length of time to retain data such as interceptions and all related records (token, prompt, tool use).
+
+### --audit-logs-retention
+
+| | |
+|-------------|------------------------------------------|
+| Type | duration |
+| Environment | $CODER_AUDIT_LOGS_RETENTION |
+| YAML | retention.audit_logs |
+| Default | 0 |
+
+How long audit log entries are retained. Set to 0 to disable (keep indefinitely). We advise keeping audit logs for at least a year, and in accordance with your compliance requirements.
+
+### --connection-logs-retention
+
+| | |
+|-------------|-----------------------------------------------|
+| Type | duration |
+| Environment | $CODER_CONNECTION_LOGS_RETENTION |
+| YAML | retention.connection_logs |
+| Default | 0 |
+
+How long connection log entries are retained. Set to 0 to disable (keep indefinitely).
+
+### --api-keys-retention
+
+| | |
+|-------------|----------------------------------------|
+| Type | duration |
+| Environment | $CODER_API_KEYS_RETENTION |
+| YAML | retention.api_keys |
+| Default | 7d |
+
+How long expired API keys are retained before being deleted. Keeping expired keys allows the backend to return a more helpful error when a user tries to use an expired key. Set to 0 to disable automatic deletion of expired keys.
diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden
index 5ed217752b..ec8f4b8cd6 100644
--- a/enterprise/cli/testdata/coder_server_--help.golden
+++ b/enterprise/cli/testdata/coder_server_--help.golden
@@ -697,6 +697,27 @@ updating, and deleting workspace resources.
Number of provisioner daemons to create on start. If builds are stuck
in queued state for a long time, consider increasing this.
+RETENTION OPTIONS:
+Configure data retention policies for various database tables. Retention
+policies automatically purge old data to reduce database size and improve
+performance. Setting a retention duration to 0 disables automatic purging for
+that data type.
+
+ --api-keys-retention duration, $CODER_API_KEYS_RETENTION (default: 7d)
+ How long expired API keys are retained before being deleted. Keeping
+ expired keys allows the backend to return a more helpful error when a
+ user tries to use an expired key. Set to 0 to disable automatic
+ deletion of expired keys.
+
+ --audit-logs-retention duration, $CODER_AUDIT_LOGS_RETENTION (default: 0)
+ How long audit log entries are retained. Set to 0 to disable (keep
+ indefinitely). We advise keeping audit logs for at least a year, and
+ in accordance with your compliance requirements.
+
+ --connection-logs-retention duration, $CODER_CONNECTION_LOGS_RETENTION (default: 0)
+ How long connection log entries are retained. Set to 0 to disable
+ (keep indefinitely).
+
TELEMETRY OPTIONS:
Telemetry is critical to our ability to improve Coder. We strip all personal
information before sending data to our servers. Please only disable telemetry
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index 456d1a737a..400ad8bb79 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -1778,6 +1778,7 @@ export interface DeploymentValues {
readonly web_terminal_renderer?: string;
readonly allow_workspace_renames?: boolean;
readonly healthcheck?: HealthcheckConfig;
+ readonly retention?: RetentionConfig;
readonly cli_upgrade_message?: string;
readonly terms_of_service_url?: string;
readonly notifications?: NotificationsConfig;
@@ -4156,6 +4157,32 @@ export interface Response {
readonly validations?: readonly ValidationError[];
}
+// From codersdk/deployment.go
+/**
+ * RetentionConfig contains configuration for data retention policies.
+ * These settings control how long various types of data are retained in the database
+ * before being automatically purged. Setting a value to 0 disables retention for that
+ * data type (data is kept indefinitely).
+ */
+export interface RetentionConfig {
+ /**
+ * AuditLogs controls how long audit log entries are retained.
+ * Set to 0 to disable (keep indefinitely).
+ */
+ readonly audit_logs: number;
+ /**
+ * ConnectionLogs controls how long connection log entries are retained.
+ * Set to 0 to disable (keep indefinitely).
+ */
+ readonly connection_logs: number;
+ /**
+ * APIKeys controls how long expired API keys are retained before being deleted.
+ * Keys are only deleted if they have been expired for at least this duration.
+ * Defaults to 7 days to preserve existing behavior.
+ */
+ readonly api_keys: number;
+}
+
// From codersdk/roles.go
/**
* Role is a longer form of SlimRole that includes permissions details.