rise-deploy 0.16.1

A simple and powerful CLI for deploying containerized applications
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# Configuration Guide

Rise backend uses YAML configuration files with environment variable substitution support. TOML is also supported for backward compatibility.

## Configuration Files

Configuration files are located in `config/` and loaded in this order:

1. `{RISE_CONFIG_RUN_MODE}.{toml,yaml,yml}` - Environment-specific config (**required**)
   - `development.toml` or `development.yaml` when `RISE_CONFIG_RUN_MODE=development`
   - `production.toml` or `production.yaml` when `RISE_CONFIG_RUN_MODE=production`
2. `local.{toml,yaml,yml}` - Local overrides (not checked into git, optional)

Later files override earlier ones.

In container deployments, `RISE_CONFIG_DIR` is typically `/etc/rise`.

**File Format**: The backend supports both YAML and TOML formats. When multiple formats exist for the same config file name (for example `development.yaml` and `development.toml`), TOML takes precedence. YAML is the recommended format as it integrates seamlessly with Kubernetes/Helm deployments.

## Environment Variable Substitution

Configuration values can reference environment variables using the syntax:

```toml
# TOML example
client_secret = "${RISE_AUTH_CLIENT_SECRET:-rise-backend-secret}"
account_id = "${AWS_ACCOUNT_ID}"
public_url = "https://${DOMAIN_NAME}:${PORT}"
```

```yaml
# YAML example
auth:
  client_secret: "${RISE_AUTH_CLIENT_SECRET:-rise-backend-secret}"
registry:
  account_id: "${AWS_ACCOUNT_ID}"
server:
  public_url: "https://${DOMAIN_NAME}:${PORT}"
```

### Syntax

- `${VAR_NAME}` - Use environment variable `VAR_NAME`, error if not set
- `${VAR_NAME:-default}` - Use `VAR_NAME` if set, otherwise use `default`

### How It Works

1. Configuration files are parsed as TOML or YAML
2. String values are scanned for `${...}` patterns
3. Patterns are replaced with environment variable values
4. Resulting configuration is deserialized into Settings struct

This happens **after** TOML/YAML parsing but **before** deserialization, so:
- ✅ Works in all string values (including nested tables/maps and arrays)
- ✅ Preserves structure and types
- ✅ Clear error messages if required variables are missing

## Configuration Precedence

Configuration is loaded in this order (later values override earlier ones):

1. `{RISE_CONFIG_RUN_MODE}.{toml,yaml,yml}` - Active environment config (required)
2. `local.{toml,yaml,yml}` - Local overrides (not in git, optional)
3. Environment variable substitution - `${VAR}` patterns are replaced
4. DATABASE_URL special case - Overrides `[database] url` if set

**Note**: When multiple file formats exist for the same config file, TOML takes precedence over YAML.

Example (TOML):
```toml
# In production.toml
client_secret = "${AUTH_SECRET}" # Required in production

# In local.toml
client_secret = "my-local-secret"  # Override: hardcoded value
```

Example (YAML):
```yaml
# In production.yaml
auth:
  client_secret: "${AUTH_SECRET}"  # Required
```

### Special Cases

**DATABASE_URL**: For convenience, the DATABASE_URL environment variable is checked after config loading and will override any `[database] url` setting. This is optional - you can use `${DATABASE_URL}` in TOML instead:

```toml
# Option 1: Direct environment variable (checked after config loads)
[database]
url = ""  # Empty, DATABASE_URL env var will be used

# Option 2: Explicit substitution (recommended for consistency)
[database]
url = "${DATABASE_URL}"
```

**Note**: DATABASE_URL is only required at compile time for SQLX query verification. At runtime, you can set it via either method above.

## Examples

### Development (development.toml)

```toml
[server]
host = "0.0.0.0"
port = 3000
public_url = "http://localhost:3000"

[auth]
issuer = "http://localhost:5556/dex"
client_id = "rise-backend"
client_secret = "${RISE_AUTH_CLIENT_SECRET:-rise-backend-secret}"
```

### Production with Environment Variables (TOML)

```toml
# production.toml
[server]
host = "0.0.0.0"
port = "${PORT:-3000}"
public_url = "${PUBLIC_URL}"  # Required, no default
cookie_secure = true

[auth]
issuer = "${DEX_ISSUER}"
client_id = "${OIDC_CLIENT_ID}"
client_secret = "${OIDC_CLIENT_SECRET}"  # Required
admin_users = ["${ADMIN_EMAIL}"]

[registry]
type = "ecr"
region = "${AWS_REGION:-us-east-1}"
account_id = "${AWS_ACCOUNT_ID}"
role_arn = "${ECR_CONTROLLER_ROLE_ARN}"
push_role_arn = "${ECR_PUSH_ROLE_ARN}"
```

### Production with Environment Variables (YAML)

```yaml
# production.yaml - ideal for Kubernetes/Helm deployments
server:
  host: "0.0.0.0"
  port: "${PORT:-3000}"
  public_url: "${PUBLIC_URL}"  # Required, no default
  cookie_secure: true

auth:
  issuer: "${DEX_ISSUER}"
  client_id: "${OIDC_CLIENT_ID}"
  client_secret: "${OIDC_CLIENT_SECRET}"
  admin_users:
    - "${ADMIN_EMAIL}"

database:
  url: "${DATABASE_URL}"

registry:
  type: "ecr"
  region: "${AWS_REGION:-us-east-1}"
  account_id: "${AWS_ACCOUNT_ID}"
  role_arn: "${ECR_CONTROLLER_ROLE_ARN}"
  push_role_arn: "${ECR_PUSH_ROLE_ARN}"
```

Environment file:
```bash
# .env
PUBLIC_URL=https://rise.example.com
DEX_ISSUER=https://dex.example.com
OIDC_CLIENT_ID=rise-production
OIDC_CLIENT_SECRET=very-secret-value
ADMIN_EMAIL=admin@example.com
AWS_ACCOUNT_ID=123456789012
ECR_CONTROLLER_ROLE_ARN=arn:aws:iam::123456789012:policy/rise-backend
ECR_PUSH_ROLE_ARN=arn:aws:iam::123456789012:role/rise-backend-ecr-push
DATABASE_URL=postgres://rise:${DB_PASSWORD}@db.example.com/rise
```

### Local Overrides (local.toml)

For local development, create `local.toml` (not checked into git):

```toml
# Override just what you need
[auth]
client_secret = "my-local-secret"

[registry]
type = "oci-client-auth"
registry_url = "localhost:5000"
```

## Configuration Reference

### Server Settings

```toml
[server]
host = "0.0.0.0"              # Bind address
port = 3000                    # HTTP port
public_url = "http://..."      # Public URL (for OAuth redirects)
cookie_domain = ""             # Cookie domain ("" = current host only)
cookie_secure = false          # Set true for HTTPS
jwt_signing_secret = "..."     # JWT signing secret (base64-encoded, min 32 bytes)
jwt_expiry_seconds = 86400     # JWT expiry duration in seconds (default: 24 hours)
jwt_claims = ["sub", "email", "name"]  # Claims to include from IdP
rs256_private_key_pem = "..."  # Optional: RS256 private key (persists JWTs across restarts)
rs256_public_key_pem = "..."   # Optional: RS256 public key (derived if not provided)
docs_dir = "/var/rise/docs"    # Optional: directory to serve documentation from
```

**Documentation Serving (`docs_dir`):**
- When set, the backend serves markdown files from the specified directory at `/static/docs/*`
- In the container image, docs are copied to `/var/rise/docs`
- In development, set to `"docs"` to serve from the repository's `docs/` directory
- If not set, documentation endpoints return 404

**JWT Configuration:**
- `jwt_signing_secret`: Base64-encoded secret for HS256 JWT signing (generate with `openssl rand -base64 32`)
- `jwt_expiry_seconds`: Duration in seconds before JWTs expire (default: 86400 = 24 hours)
- `jwt_claims`: Claims to include from IdP token in Rise JWTs
- `rs256_private_key_pem`: Optional pre-configured RS256 private key (prevents JWT invalidation on restart)
- `rs256_public_key_pem`: Optional RS256 public key (automatically derived from private key if omitted)

### Auth Settings

```toml
[auth]
issuer = "http://..."          # OIDC issuer URL
client_id = "rise-backend"     # OAuth2 client ID
client_secret = "..."          # OAuth2 client secret
admin_users = ["email@..."]    # Admin user emails (array)
allow_team_creation = true     # Allow regular users to create teams (default: true)
                              # When false, only admins can create teams
```

**Team Creation Control:**
- `allow_team_creation = true` (default): All authenticated users can create teams
- `allow_team_creation = false`: Only admin users can create teams (suitable for centrally-managed organizations)

### Database Settings

```toml
[database]
url = "postgres://..."         # PostgreSQL connection string
                              # Or use DATABASE_URL env var
```

### Registry Settings

#### AWS ECR

```toml
[registry]
type = "ecr"
region = "us-east-1"
account_id = "123456789012"
repo_prefix = "rise/"
role_arn = "arn:aws:iam::..."
push_role_arn = "arn:aws:iam::..."
auto_remove = true
```

#### OCI Registry (Docker, Harbor, Quay)

```toml
[registry]
type = "oci-client-auth"
registry_url = "registry.example.com"
namespace = "rise-apps"
```

### Controller Settings (Optional)

```toml
[controller]
reconcile_interval_secs = 5
health_check_interval_secs = 5
termination_interval_secs = 5
cancellation_interval_secs = 5
expiration_interval_secs = 60
secret_refresh_interval_secs = 3600
```

## Validation

The backend validates configuration on startup:
- Required fields must be set
- Invalid values cause startup failure with clear error messages
- Environment variable substitution errors are reported
- **Unknown configuration fields generate warnings** (as of v0.9.0)

### Checking Configuration

Use the `rise backend check-config` command to validate backend configuration:

```bash
rise backend check-config
```

This command:
- Loads and validates backend configuration files
- Reports any unknown/unused configuration fields as warnings
- Exits with an error if configuration is invalid
- Useful for CI/CD pipelines and deployment validation

Example output:
```
Checking backend configuration...
⚠️  WARN: Unknown configuration field in backend config: server.typo_field
⚠️  WARN: Unknown configuration field in backend config: unknown_section
✓ Configuration is valid
```

### JSON Schema

Rise provides a JSON Schema for backend configuration at:

- [`docs/schemas/backend-settings.schema.json`]schemas/backend-settings.schema.json

Generate it with:

```bash
cargo run --features cli,backend -- backend config-schema > docs/schemas/backend-settings.schema.json
```

CI verifies this file is up to date on every PR and push.

### Unknown Field Warnings

Starting in v0.9.0, Rise warns about unrecognized configuration fields to help catch typos and outdated options:

**Backend Configuration (YAML/TOML):**
```bash
# Warnings appear in logs when starting server or using check-config
WARN rise::server::settings: Unknown configuration field in backend config: server.unknown_field
```

**Project Configuration (rise.toml):**
```bash
# Warnings appear when loading rise.toml (during build, deploy, etc.)
WARN rise::build::config: Unknown configuration field in ./rise.toml: build.?.typo_field
```

These are warnings, not errors - your configuration will still load and work. The warnings help you:
- Catch typos in field names
- Identify outdated configuration options after upgrades
- Ensure your configuration is being used as intended

Run with `RUST_LOG=debug` to see configuration loading details:

```bash
RUST_LOG=debug cargo run --bin rise -- backend server
```

## Custom Domains

Rise supports custom domains for projects, allowing you to serve your applications from your own domain names instead of (or in addition to) the default project URL.

### Primary Custom Domains

Each project can designate one custom domain as **primary**. The primary domain is used as the canonical URL for the application and is exposed via the `RISE_APP_URL` environment variable.

### RISE_APP_URL Environment Variable

Rise automatically creates a `RISE_APP_URL` deployment environment variable containing the canonical URL for the application. This variable is determined at deployment creation time and persisted in the database:

- **If a primary custom domain is set**: `RISE_APP_URL` contains the primary custom domain URL (e.g., `https://example.com`)
- **If no primary domain is set**: `RISE_APP_URL` contains the default project URL (e.g., `https://my-app.rise.dev`)

Since this is a deployment environment variable, you can view it via the API or CLI along with your other environment variables.

This environment variable is useful for:
- Generating absolute URLs in your application (e.g., for email links, OAuth redirects)
- Implementing canonical URL redirects (redirect all traffic to the primary domain)
- Setting the correct domain for cookies and CORS headers

**Example usage in your application:**

```javascript
// Node.js
const canonicalUrl = process.env.RISE_APP_URL;

// Redirect to canonical domain
app.use((req, res, next) => {
  const requestUrl = `${req.protocol}://${req.get('host')}`;
  if (requestUrl !== canonicalUrl) {
    return res.redirect(301, `${canonicalUrl}${req.url}`);
  }
  next();
});
```

```python
# Python
import os

canonical_url = os.environ.get('RISE_APP_URL')

# Flask: Set SERVER_NAME
app.config['SERVER_NAME'] = canonical_url.replace('https://', '').replace('http://', '')
```

### Managing Custom Domains

**Via Frontend:**
1. Navigate to your project's Domains tab
2. Add custom domains using the "Add Domain" button
3. Click the star icon next to a domain to set it as primary
4. The primary domain will show a filled yellow star and a "Primary" badge

**Via API:**

```bash
# List custom domains
curl https://rise.dev/api/projects/my-app/domains

# Add a custom domain
curl -X POST https://rise.dev/api/projects/my-app/domains \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"domain": "example.com"}'

# Set domain as primary
curl -X PUT https://rise.dev/api/projects/my-app/domains/example.com/primary \
  -H "Authorization: Bearer $TOKEN"

# Unset primary status
curl -X DELETE https://rise.dev/api/projects/my-app/domains/example.com/primary \
  -H "Authorization: Bearer $TOKEN"
```

### DNS Configuration

Before adding a custom domain, you must configure your DNS to point to your Rise deployment:

```
# A record for root domain
example.com.  IN  A  <rise-ingress-ip>

# CNAME for subdomain
www.example.com.  IN  CNAME  <rise-ingress-hostname>
```

Custom domains are added to the ingress for the default deployment group only.

### TLS/SSL

Custom domains use the same TLS configuration as the default project URL:
- If your Rise deployment uses a wildcard certificate, custom domains will use HTTP unless configured with per-domain TLS
- Configure `custom_domain_tls_mode` in the Kubernetes controller settings for automatic HTTPS on custom domains

### Behavior

- **Automatic reconciliation**: Setting or unsetting a primary domain triggers reconciliation of the active deployment to update the `RISE_APP_URL` environment variable
- **Deletion protection**: You can delete a primary domain; `RISE_APP_URL` will fall back to the default project URL
- **Multiple domains**: You can add multiple custom domains to a project, but only one can be primary
- **Environment variable list**: All custom domains (primary and non-primary) are also available in the `RISE_APP_URLS` environment variable as a JSON array