or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-data.mdconfiguration.mdcore-parsing.mdcustom-parsers.mdfile-secrets.mdframework-integration.mdindex.mdspecialized-types.mdvalidation.md

file-secrets.mddocs/

0

# File-Based Secrets

1

2

Docker-style secret file support for reading sensitive configuration from mounted files instead of environment variables, enabling secure handling of passwords, API keys, and certificates in containerized environments.

3

4

## Capabilities

5

6

### FileAwareEnv Class

7

8

Extended environment reader that supports reading values from files when corresponding `_FILE` environment variables are set.

9

10

```python { .api }

11

class FileAwareEnv(Env):

12

def __init__(self, *, file_suffix="_FILE", eager=True, expand_vars=False, prefix=None):

13

"""

14

Initialize FileAwareEnv with file reading capabilities.

15

16

Parameters:

17

- file_suffix: str, suffix appended to variable names for file paths (default: "_FILE")

18

- eager: bool, whether to raise validation errors immediately (default: True)

19

- expand_vars: bool, whether to expand ${VAR} syntax (default: False)

20

- prefix: str, global prefix for all variable names (default: None)

21

22

Behavior:

23

When parsing variable "SECRET", first checks for "SECRET_FILE" environment variable.

24

If "SECRET_FILE" exists and points to readable file, returns file contents.

25

Otherwise falls back to standard "SECRET" environment variable.

26

"""

27

```

28

29

Usage examples:

30

31

```python

32

import os

33

from environs import FileAwareEnv

34

from pathlib import Path

35

36

# Create temporary secret files for demonstration

37

Path("/tmp/db_password").write_text("super_secret_password")

38

Path("/tmp/api_key").write_text("sk_live_123456789abcdef")

39

40

# Set up file-based environment variables

41

os.environ["DATABASE_PASSWORD_FILE"] = "/tmp/db_password"

42

os.environ["API_KEY_FILE"] = "/tmp/api_key"

43

os.environ["REGULAR_CONFIG"] = "not_from_file"

44

45

# Initialize FileAwareEnv

46

file_env = FileAwareEnv()

47

48

# Read from files

49

db_password = file_env.str("DATABASE_PASSWORD") # Reads from /tmp/db_password

50

api_key = file_env.str("API_KEY") # Reads from /tmp/api_key

51

regular = file_env.str("REGULAR_CONFIG") # Reads from environment variable

52

53

print(f"Database password: {db_password}") # => "super_secret_password"

54

print(f"API key: {api_key}") # => "sk_live_123456789abcdef"

55

print(f"Regular config: {regular}") # => "not_from_file"

56

57

# Clean up

58

Path("/tmp/db_password").unlink()

59

Path("/tmp/api_key").unlink()

60

```

61

62

### Custom File Suffix

63

64

Customize the suffix used for file environment variables to match your naming conventions.

65

66

```python

67

import os

68

from environs import FileAwareEnv

69

from pathlib import Path

70

71

# Custom suffix example

72

Path("/tmp/secret").write_text("my_secret_value")

73

os.environ["API_SECRET_PATH"] = "/tmp/secret"

74

75

# Initialize with custom suffix

76

custom_env = FileAwareEnv(file_suffix="_PATH")

77

78

# Read using custom suffix

79

secret = custom_env.str("API_SECRET") # Looks for API_SECRET_PATH

80

print(f"Secret: {secret}") # => "my_secret_value"

81

82

# Clean up

83

Path("/tmp/secret").unlink()

84

```

85

86

### Docker Secrets Integration

87

88

Integrate with Docker Swarm secrets or Kubernetes mounted secrets for production deployments.

89

90

```python

91

import os

92

from environs import FileAwareEnv

93

94

# Docker Swarm secrets are typically mounted at /run/secrets/

95

os.environ["DATABASE_PASSWORD_FILE"] = "/run/secrets/db_password"

96

os.environ["SSL_CERT_FILE"] = "/run/secrets/ssl_certificate"

97

os.environ["SSL_KEY_FILE"] = "/run/secrets/ssl_private_key"

98

99

# Kubernetes secrets mounted as files

100

os.environ["JWT_SECRET_FILE"] = "/etc/secrets/jwt_secret"

101

os.environ["OAUTH_CLIENT_SECRET_FILE"] = "/etc/secrets/oauth_client_secret"

102

103

file_env = FileAwareEnv()

104

105

# Read secrets from mounted files

106

try:

107

db_password = file_env.str("DATABASE_PASSWORD")

108

ssl_cert = file_env.str("SSL_CERT")

109

ssl_key = file_env.str("SSL_KEY")

110

jwt_secret = file_env.str("JWT_SECRET")

111

oauth_secret = file_env.str("OAUTH_CLIENT_SECRET")

112

113

print("All secrets loaded successfully from files")

114

except Exception as e:

115

print(f"Error loading secrets: {e}")

116

```

117

118

### Fallback Behavior

119

120

FileAwareEnv gracefully falls back to regular environment variables when file variables are not set.

121

122

```python

123

import os

124

from environs import FileAwareEnv

125

from pathlib import Path

126

127

file_env = FileAwareEnv()

128

129

# Set up mixed configuration

130

Path("/tmp/file_secret").write_text("from_file")

131

os.environ["SECRET_FROM_FILE_FILE"] = "/tmp/file_secret"

132

os.environ["SECRET_FROM_ENV"] = "from_environment"

133

134

# FileAwareEnv will read from file when _FILE variable exists

135

secret1 = file_env.str("SECRET_FROM_FILE") # => "from_file"

136

137

# Falls back to environment variable when no _FILE variable

138

secret2 = file_env.str("SECRET_FROM_ENV") # => "from_environment"

139

140

# Default values work as expected

141

secret3 = file_env.str("MISSING_SECRET", "default") # => "default"

142

143

print(f"From file: {secret1}")

144

print(f"From env: {secret2}")

145

print(f"Default: {secret3}")

146

147

# Clean up

148

Path("/tmp/file_secret").unlink()

149

```

150

151

### Type Casting with File Secrets

152

153

All standard environs type casting works with file-based values.

154

155

```python

156

import os

157

from environs import FileAwareEnv

158

from pathlib import Path

159

160

# Create files with different data types

161

Path("/tmp/port_number").write_text("8080")

162

Path("/tmp/debug_flag").write_text("true")

163

Path("/tmp/allowed_hosts").write_text("localhost,127.0.0.1,example.com")

164

Path("/tmp/config_json").write_text('{"timeout": 30, "retries": 3}')

165

166

# Set up file environment variables

167

os.environ["PORT_FILE"] = "/tmp/port_number"

168

os.environ["DEBUG_FILE"] = "/tmp/debug_flag"

169

os.environ["ALLOWED_HOSTS_FILE"] = "/tmp/allowed_hosts"

170

os.environ["CONFIG_FILE"] = "/tmp/config_json"

171

172

file_env = FileAwareEnv()

173

174

# Type casting works with file contents

175

port = file_env.int("PORT") # => 8080

176

debug = file_env.bool("DEBUG") # => True

177

hosts = file_env.list("ALLOWED_HOSTS") # => ["localhost", "127.0.0.1", "example.com"]

178

config = file_env.json("CONFIG") # => {"timeout": 30, "retries": 3}

179

180

print(f"Port: {port} (type: {type(port)})")

181

print(f"Debug: {debug} (type: {type(debug)})")

182

print(f"Hosts: {hosts}")

183

print(f"Config: {config}")

184

185

# Clean up

186

for file_path in ["/tmp/port_number", "/tmp/debug_flag", "/tmp/allowed_hosts", "/tmp/config_json"]:

187

Path(file_path).unlink()

188

```

189

190

### Error Handling

191

192

Handle file-related errors appropriately with detailed error messages.

193

194

```python

195

import os

196

from environs import FileAwareEnv, EnvValidationError

197

198

file_env = FileAwareEnv()

199

200

# File doesn't exist

201

os.environ["MISSING_FILE_FILE"] = "/path/that/does/not/exist"

202

try:

203

value = file_env.str("MISSING_FILE")

204

except ValueError as e:

205

print(f"File not found: {e}")

206

207

# Permission denied

208

os.environ["RESTRICTED_FILE_FILE"] = "/root/restricted_file"

209

try:

210

value = file_env.str("RESTRICTED_FILE")

211

except ValueError as e:

212

print(f"Permission error: {e}")

213

214

# Directory instead of file

215

os.environ["DIRECTORY_FILE"] = "/tmp"

216

try:

217

value = file_env.str("DIRECTORY")

218

except ValueError as e:

219

print(f"Directory error: {e}")

220

221

# Invalid file content for type casting

222

from pathlib import Path

223

Path("/tmp/invalid_int").write_text("not_a_number")

224

os.environ["INVALID_INT_FILE"] = "/tmp/invalid_int"

225

226

try:

227

value = file_env.int("INVALID_INT")

228

except EnvValidationError as e:

229

print(f"Type casting error: {e}")

230

231

# Clean up

232

Path("/tmp/invalid_int").unlink()

233

```

234

235

### Production Deployment Example

236

237

Complete example showing how to use FileAwareEnv in a production Django application with Docker secrets.

238

239

```python

240

# settings.py for production Django app

241

import os

242

from environs import FileAwareEnv

243

244

# Initialize FileAwareEnv for production secrets

245

env = FileAwareEnv()

246

247

# Load .env file if it exists (for local development)

248

env.read_env()

249

250

# Basic settings

251

DEBUG = env.bool("DEBUG", False)

252

SECRET_KEY = env.str("SECRET_KEY") # Will read from SECRET_KEY_FILE if available

253

254

# Database configuration (password from file in production)

255

DATABASES = {

256

'default': {

257

'ENGINE': 'django.db.backends.postgresql',

258

'NAME': env.str("DB_NAME"),

259

'USER': env.str("DB_USER"),

260

'PASSWORD': env.str("DB_PASSWORD"), # From DB_PASSWORD_FILE in production

261

'HOST': env.str("DB_HOST", "localhost"),

262

'PORT': env.int("DB_PORT", 5432),

263

}

264

}

265

266

# Redis configuration (password from file)

267

REDIS_PASSWORD = env.str("REDIS_PASSWORD", "") # From REDIS_PASSWORD_FILE

268

269

# Email configuration (SMTP password from file)

270

EMAIL_HOST_PASSWORD = env.str("EMAIL_HOST_PASSWORD", "")

271

272

# SSL certificate paths (certificate content from files)

273

SSL_CERTIFICATE = env.str("SSL_CERTIFICATE", "") # From SSL_CERTIFICATE_FILE

274

SSL_PRIVATE_KEY = env.str("SSL_PRIVATE_KEY", "") # From SSL_PRIVATE_KEY_FILE

275

276

# API keys (from files in production)

277

STRIPE_SECRET_KEY = env.str("STRIPE_SECRET_KEY", "")

278

AWS_SECRET_ACCESS_KEY = env.str("AWS_SECRET_ACCESS_KEY", "")

279

```

280

281

Corresponding Docker Compose configuration:

282

283

```yaml

284

# docker-compose.yml

285

version: '3.8'

286

services:

287

web:

288

image: myapp:latest

289

environment:

290

- DEBUG=false

291

- SECRET_KEY_FILE=/run/secrets/django_secret_key

292

- DB_PASSWORD_FILE=/run/secrets/db_password

293

- REDIS_PASSWORD_FILE=/run/secrets/redis_password

294

- STRIPE_SECRET_KEY_FILE=/run/secrets/stripe_secret_key

295

secrets:

296

- django_secret_key

297

- db_password

298

- redis_password

299

- stripe_secret_key

300

301

secrets:

302

django_secret_key:

303

external: true

304

db_password:

305

external: true

306

redis_password:

307

external: true

308

stripe_secret_key:

309

external: true

310

```

311

312

### Local Development with Files

313

314

Use file-based secrets in local development for consistency with production.

315

316

```python

317

# Local development setup

318

import os

319

from environs import FileAwareEnv

320

from pathlib import Path

321

322

# Create local secrets directory

323

secrets_dir = Path("./secrets")

324

secrets_dir.mkdir(exist_ok=True)

325

326

# Write development secrets to files

327

(secrets_dir / "db_password").write_text("dev_password_123")

328

(secrets_dir / "api_key").write_text("dev_api_key_456")

329

(secrets_dir / "jwt_secret").write_text("dev_jwt_secret_789")

330

331

# Set environment to use local secret files

332

os.environ["DATABASE_PASSWORD_FILE"] = str(secrets_dir / "db_password")

333

os.environ["API_KEY_FILE"] = str(secrets_dir / "api_key")

334

os.environ["JWT_SECRET_FILE"] = str(secrets_dir / "jwt_secret")

335

336

# Use FileAwareEnv as in production

337

env = FileAwareEnv()

338

339

db_password = env.str("DATABASE_PASSWORD")

340

api_key = env.str("API_KEY")

341

jwt_secret = env.str("JWT_SECRET")

342

343

print("Local development secrets loaded from files")

344

```

345

346

## Types

347

348

```python { .api }

349

from pathlib import Path

350

from typing import Union

351

352

FilePath = Union[str, Path]

353

```