feat(env): Enhance environment variable management with automatic detection and merging
- Implement functionality to automatically read and merge environment variables from Dockerfile ENV directives with user-provided .env files. - Add logging to inspect and debug environment variables during container deployment. - Update README and examples to reflect new environment variable features and usage instructions, including smart merging and secret integration.
- commit
- 0da72ac1e549af4bccfd0b8351c656efcfb027bc
- author
- bamen · · 2025-06-08 14:59 UTC
- parent
- e7ac942
- signature
- unsigned
7 files · +224 -2
M README.md +29 -0
| @@ -43,6 +43,7 @@ podman push registry.mydomain.com/myapp:latest | |||
| 43 | 43 | - **Zero-Downtime Updates**: Graceful container swaps | |
| 44 | 44 | - **Auto-Route Creation** (Optional): Push `myapp.mydomain.com:latest` → route created automatically | |
| 45 | 45 | - **Auto-Volume Management**: Zero-config persistent storage from Dockerfile VOLUME directives | |
| 46 | + | - **Auto-Environment Injection**: Dockerfile ENV directives automatically merge with your custom environment variables | |
| 46 | 47 | ||
| 47 | 48 | ## Quick Start (5 minutes) | |
| 48 | 49 | ||
| @@ -291,6 +292,22 @@ preserve = true # Default: true (keep volumes when removing containers) | |||
| 291 | 292 | ||
| 292 | 293 | **Most users never need to touch this configuration** - the defaults work perfectly for 99% of use cases. | |
| 293 | 294 | ||
| 295 | + | ## Smart Environment & Volume Management | |
| 296 | + | ||
| 297 | + | Gordon automatically handles both environment variables and persistent storage with zero configuration: | |
| 298 | + | ||
| 299 | + | ### Environment Variables | |
| 300 | + | - **Auto-Detection**: Reads `ENV` directives from your Dockerfile | |
| 301 | + | - **Smart Merging**: Your `.env` files override Dockerfile ENV values | |
| 302 | + | - **Secret Integration**: Supports `pass` and `SOPS` for sensitive data | |
| 303 | + | ||
| 304 | + | ### Persistent Volumes | |
| 305 | + | - **Auto-Detection**: Reads `VOLUME` directives from your Dockerfile | |
| 306 | + | - **Zero Configuration**: Creates and manages volumes automatically | |
| 307 | + | - **Data Persistence**: Survives container updates and reboots | |
| 308 | + | ||
| 309 | + | See [examples/](examples/) for detailed configuration examples. | |
| 310 | + | ||
| 294 | 311 | ## Advanced Configuration | |
| 295 | 312 | ||
| 296 | 313 | ### Full Config Structure (with default values) | |
| @@ -378,6 +395,18 @@ A: Volumes are preserved by default. Your data persists across updates and reboo | |||
| 378 | 395 | **Q: How do I backup volume data?** | |
| 379 | 396 | A: Volumes are regular Docker/Podman volumes - use standard backup tools like `docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .` | |
| 380 | 397 | ||
| 398 | + | **Q: Do Dockerfile ENV directives work automatically?** | |
| 399 | + | A: Yes! Gordon reads ENV directives from your images and merges them with your custom .env files. | |
| 400 | + | ||
| 401 | + | **Q: What happens if I set the same environment variable in both Dockerfile and .env?** | |
| 402 | + | A: Your .env file always wins - custom environment variables override Dockerfile ENV directives. | |
| 403 | + | ||
| 404 | + | **Q: Can I see what environment variables are being applied?** | |
| 405 | + | A: Yes, check Gordon's logs - it shows both Dockerfile ENV and final merged environment variables. | |
| 406 | + | ||
| 407 | + | **Q: Do I need to rebuild my images to change environment variables?** | |
| 408 | + | A: No! Just update your .env files and reload Gordon. Dockerfile ENV provides defaults; .env provides overrides. | |
| 409 | + | ||
| 381 | 410 | ## Philosophy | |
| 382 | 411 | ||
| 383 | 412 | ```bash | |
| @@ -43,6 +43,7 @@ podman push registry.mydomain.com/myapp:latest | |||||
| 43 | - **Zero-Downtime Updates**: Graceful container swaps | 43 | - **Zero-Downtime Updates**: Graceful container swaps | ||
| 44 | - **Auto-Route Creation** (Optional): Push `myapp.mydomain.com:latest` → route created automatically | 44 | - **Auto-Route Creation** (Optional): Push `myapp.mydomain.com:latest` → route created automatically | ||
| 45 | - **Auto-Volume Management**: Zero-config persistent storage from Dockerfile VOLUME directives | 45 | - **Auto-Volume Management**: Zero-config persistent storage from Dockerfile VOLUME directives | ||
| 46 | + | - **Auto-Environment Injection**: Dockerfile ENV directives automatically merge with your custom environment variables | |||
| 46 | 47 | ||||
| 47 | ## Quick Start (5 minutes) | 48 | ## Quick Start (5 minutes) | ||
| 48 | 49 | ||||
| @@ -291,6 +292,22 @@ preserve = true # Default: true (keep volumes when removing containers) | |||||
| 291 | 292 | ||||
| 292 | **Most users never need to touch this configuration** - the defaults work perfectly for 99% of use cases. | 293 | **Most users never need to touch this configuration** - the defaults work perfectly for 99% of use cases. | ||
| 293 | 294 | ||||
| 295 | + | ## Smart Environment & Volume Management | |||
| 296 | + | ||||
| 297 | + | Gordon automatically handles both environment variables and persistent storage with zero configuration: | |||
| 298 | + | ||||
| 299 | + | ### Environment Variables | |||
| 300 | + | - **Auto-Detection**: Reads `ENV` directives from your Dockerfile | |||
| 301 | + | - **Smart Merging**: Your `.env` files override Dockerfile ENV values | |||
| 302 | + | - **Secret Integration**: Supports `pass` and `SOPS` for sensitive data | |||
| 303 | + | ||||
| 304 | + | ### Persistent Volumes | |||
| 305 | + | - **Auto-Detection**: Reads `VOLUME` directives from your Dockerfile | |||
| 306 | + | - **Zero Configuration**: Creates and manages volumes automatically | |||
| 307 | + | - **Data Persistence**: Survives container updates and reboots | |||
| 308 | + | ||||
| 309 | + | See [examples/](examples/) for detailed configuration examples. | |||
| 310 | + | ||||
| 294 | ## Advanced Configuration | 311 | ## Advanced Configuration | ||
| 295 | 312 | ||||
| 296 | ### Full Config Structure (with default values) | 313 | ### Full Config Structure (with default values) | ||
| @@ -378,6 +395,18 @@ A: Volumes are preserved by default. Your data persists across updates and reboo | |||||
| 378 | **Q: How do I backup volume data?** | 395 | **Q: How do I backup volume data?** | ||
| 379 | A: Volumes are regular Docker/Podman volumes - use standard backup tools like `docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .` | 396 | A: Volumes are regular Docker/Podman volumes - use standard backup tools like `docker run --rm -v volume_name:/data -v $(pwd):/backup alpine tar czf /backup/backup.tar.gz -C /data .` | ||
| 380 | 397 | ||||
| 398 | + | **Q: Do Dockerfile ENV directives work automatically?** | |||
| 399 | + | A: Yes! Gordon reads ENV directives from your images and merges them with your custom .env files. | |||
| 400 | + | ||||
| 401 | + | **Q: What happens if I set the same environment variable in both Dockerfile and .env?** | |||
| 402 | + | A: Your .env file always wins - custom environment variables override Dockerfile ENV directives. | |||
| 403 | + | ||||
| 404 | + | **Q: Can I see what environment variables are being applied?** | |||
| 405 | + | A: Yes, check Gordon's logs - it shows both Dockerfile ENV and final merged environment variables. | |||
| 406 | + | ||||
| 407 | + | **Q: Do I need to rebuild my images to change environment variables?** | |||
| 408 | + | A: No! Just update your .env files and reload Gordon. Dockerfile ENV provides defaults; .env provides overrides. | |||
| 409 | + | ||||
| 381 | ## Philosophy | 410 | ## Philosophy | ||
| 382 | 411 | ||||
| 383 | ```bash | 412 | ```bash | ||
M examples/README.md +109 -1
| @@ -130,12 +130,120 @@ compress = true # Compress old log files | |||
| 130 | 130 | See [`logging.toml`](logging.toml) for comprehensive logging examples. | |
| 131 | 131 | ||
| 132 | 132 | ### Environment Variables | |
| 133 | - | For production, use environment variables for sensitive data: | |
| 133 | + | Gordon intelligently merges environment variables from multiple sources: | |
| 134 | + | ||
| 135 | + | 1. **Dockerfile ENV directives** (automatically detected) | |
| 136 | + | 2. **Your .env files** (override Dockerfile ENV) | |
| 137 | + | 3. **Runtime environment** (lowest priority) | |
| 138 | + | ||
| 139 | + | #### Dockerfile ENV (Automatic Detection) | |
| 140 | + | ```dockerfile | |
| 141 | + | FROM node:18-alpine | |
| 142 | + | WORKDIR /app | |
| 143 | + | ||
| 144 | + | # Gordon automatically reads these ENV directives | |
| 145 | + | ENV NODE_ENV=production | |
| 146 | + | ENV PORT=3000 | |
| 147 | + | ENV LOG_LEVEL=info | |
| 148 | + | ENV DATABASE_HOST=localhost | |
| 149 | + | ||
| 150 | + | COPY package*.json ./ | |
| 151 | + | RUN npm install | |
| 152 | + | COPY . . | |
| 153 | + | EXPOSE 3000 | |
| 154 | + | CMD ["npm", "start"] | |
| 155 | + | ``` | |
| 156 | + | ||
| 157 | + | #### Custom Environment Files | |
| 158 | + | Create `.env` files for each route: `./data/env/app_yourdomain_com.env` | |
| 159 | + | ```bash | |
| 160 | + | # Your custom overrides (these take precedence over Dockerfile ENV) | |
| 161 | + | DATABASE_HOST=db.mydomain.com | |
| 162 | + | DATABASE_USER=myapp | |
| 163 | + | DATABASE_PASSWORD=${pass:myapp/db-password} | |
| 164 | + | API_KEY=${sops:secrets.yaml:api_key} | |
| 165 | + | # NODE_ENV, PORT, LOG_LEVEL from Dockerfile are preserved | |
| 166 | + | ``` | |
| 167 | + | ||
| 168 | + | #### Final Applied Environment | |
| 169 | + | ```bash | |
| 170 | + | NODE_ENV=production # From Dockerfile ENV | |
| 171 | + | PORT=3000 # From Dockerfile ENV | |
| 172 | + | LOG_LEVEL=info # From Dockerfile ENV | |
| 173 | + | DATABASE_HOST=db.mydomain.com # From .env (overridden) | |
| 174 | + | DATABASE_USER=myapp # From .env (new) | |
| 175 | + | DATABASE_PASSWORD=secret123 # From .env via pass | |
| 176 | + | API_KEY=abc123 # From .env via SOPS | |
| 177 | + | ``` | |
| 178 | + | ||
| 179 | + | #### Production Environment Variables | |
| 180 | + | For sensitive production data: | |
| 134 | 181 | ```bash | |
| 135 | 182 | export GORDON_REGISTRY_PASSWORD="your-secure-password" | |
| 136 | 183 | export GORDON_SSL_EMAIL="admin@your-domain.com" | |
| 137 | 184 | ``` | |
| 138 | 185 | ||
| 186 | + | ### Volume Management | |
| 187 | + | Gordon automatically creates persistent volumes from Dockerfile VOLUME directives: | |
| 188 | + | ||
| 189 | + | #### Dockerfile VOLUME (Automatic Detection) | |
| 190 | + | ```dockerfile | |
| 191 | + | FROM postgres:15 | |
| 192 | + | # Gordon automatically creates persistent volumes for these paths | |
| 193 | + | VOLUME ["/var/lib/postgresql/data"] | |
| 194 | + | ||
| 195 | + | # Multi-volume example | |
| 196 | + | FROM myapp:latest | |
| 197 | + | VOLUME ["/app/data", "/app/logs", "/app/uploads"] | |
| 198 | + | ``` | |
| 199 | + | ||
| 200 | + | #### Volume Features | |
| 201 | + | - **Zero Configuration**: Reads VOLUME directives automatically | |
| 202 | + | - **Persistent Storage**: Data survives container restarts and updates | |
| 203 | + | - **Predictable Naming**: `gordon-{domain}-{path-hash}` format | |
| 204 | + | - **Cross-Platform**: Works with Docker and Podman | |
| 205 | + | ||
| 206 | + | #### Volume Configuration (Optional) | |
| 207 | + | ```toml | |
| 208 | + | [volumes] | |
| 209 | + | auto_create = true # Default: true (automatically handle VOLUME directives) | |
| 210 | + | prefix = "gordon" # Default: "gordon" (volume name prefix) | |
| 211 | + | preserve = true # Default: true (keep volumes when containers are removed) | |
| 212 | + | ``` | |
| 213 | + | ||
| 214 | + | #### Complete Dockerfile Example | |
| 215 | + | ```dockerfile | |
| 216 | + | FROM node:18-alpine | |
| 217 | + | WORKDIR /app | |
| 218 | + | ||
| 219 | + | # Environment variables (automatically merged with your .env files) | |
| 220 | + | ENV NODE_ENV=production | |
| 221 | + | ENV PORT=3000 | |
| 222 | + | ENV LOG_LEVEL=info | |
| 223 | + | ||
| 224 | + | # Install dependencies | |
| 225 | + | COPY package*.json ./ | |
| 226 | + | RUN npm install | |
| 227 | + | ||
| 228 | + | # Copy application code | |
| 229 | + | COPY . . | |
| 230 | + | ||
| 231 | + | # Persistent storage (automatically managed by Gordon) | |
| 232 | + | VOLUME ["/app/data", "/app/logs"] | |
| 233 | + | ||
| 234 | + | # Network configuration | |
| 235 | + | EXPOSE 3000 | |
| 236 | + | ||
| 237 | + | # Start command | |
| 238 | + | CMD ["npm", "start"] | |
| 239 | + | ``` | |
| 240 | + | ||
| 241 | + | When you deploy this image: | |
| 242 | + | - Gordon reads ENV directives and merges with your .env files | |
| 243 | + | - Gordon creates volumes for `/app/data` and `/app/logs` | |
| 244 | + | - Your data persists across deployments and reboots | |
| 245 | + | - Environment variables are intelligently managed | |
| 246 | + | ||
| 139 | 247 | ## 🌐 DNS Configuration | |
| 140 | 248 | ||
| 141 | 249 | ### Cloudflare Setup (Recommended) | |
| @@ -130,12 +130,120 @@ compress = true # Compress old log files | |||||
| 130 | See [`logging.toml`](logging.toml) for comprehensive logging examples. | 130 | See [`logging.toml`](logging.toml) for comprehensive logging examples. | ||
| 131 | 131 | ||||
| 132 | ### Environment Variables | 132 | ### Environment Variables | ||
| 133 | - | For production, use environment variables for sensitive data: | 133 | + | Gordon intelligently merges environment variables from multiple sources: |
| 134 | + | ||||
| 135 | + | 1. **Dockerfile ENV directives** (automatically detected) | |||
| 136 | + | 2. **Your .env files** (override Dockerfile ENV) | |||
| 137 | + | 3. **Runtime environment** (lowest priority) | |||
| 138 | + | ||||
| 139 | + | #### Dockerfile ENV (Automatic Detection) | |||
| 140 | + | ```dockerfile | |||
| 141 | + | FROM node:18-alpine | |||
| 142 | + | WORKDIR /app | |||
| 143 | + | ||||
| 144 | + | # Gordon automatically reads these ENV directives | |||
| 145 | + | ENV NODE_ENV=production | |||
| 146 | + | ENV PORT=3000 | |||
| 147 | + | ENV LOG_LEVEL=info | |||
| 148 | + | ENV DATABASE_HOST=localhost | |||
| 149 | + | ||||
| 150 | + | COPY package*.json ./ | |||
| 151 | + | RUN npm install | |||
| 152 | + | COPY . . | |||
| 153 | + | EXPOSE 3000 | |||
| 154 | + | CMD ["npm", "start"] | |||
| 155 | + | ``` | |||
| 156 | + | ||||
| 157 | + | #### Custom Environment Files | |||
| 158 | + | Create `.env` files for each route: `./data/env/app_yourdomain_com.env` | |||
| 159 | + | ```bash | |||
| 160 | + | # Your custom overrides (these take precedence over Dockerfile ENV) | |||
| 161 | + | DATABASE_HOST=db.mydomain.com | |||
| 162 | + | DATABASE_USER=myapp | |||
| 163 | + | DATABASE_PASSWORD=${pass:myapp/db-password} | |||
| 164 | + | API_KEY=${sops:secrets.yaml:api_key} | |||
| 165 | + | # NODE_ENV, PORT, LOG_LEVEL from Dockerfile are preserved | |||
| 166 | + | ``` | |||
| 167 | + | ||||
| 168 | + | #### Final Applied Environment | |||
| 169 | + | ```bash | |||
| 170 | + | NODE_ENV=production # From Dockerfile ENV | |||
| 171 | + | PORT=3000 # From Dockerfile ENV | |||
| 172 | + | LOG_LEVEL=info # From Dockerfile ENV | |||
| 173 | + | DATABASE_HOST=db.mydomain.com # From .env (overridden) | |||
| 174 | + | DATABASE_USER=myapp # From .env (new) | |||
| 175 | + | DATABASE_PASSWORD=secret123 # From .env via pass | |||
| 176 | + | API_KEY=abc123 # From .env via SOPS | |||
| 177 | + | ``` | |||
| 178 | + | ||||
| 179 | + | #### Production Environment Variables | |||
| 180 | + | For sensitive production data: | |||
| 134 | ```bash | 181 | ```bash | ||
| 135 | export GORDON_REGISTRY_PASSWORD="your-secure-password" | 182 | export GORDON_REGISTRY_PASSWORD="your-secure-password" | ||
| 136 | export GORDON_SSL_EMAIL="admin@your-domain.com" | 183 | export GORDON_SSL_EMAIL="admin@your-domain.com" | ||
| 137 | ``` | 184 | ``` | ||
| 138 | 185 | ||||
| 186 | + | ### Volume Management | |||
| 187 | + | Gordon automatically creates persistent volumes from Dockerfile VOLUME directives: | |||
| 188 | + | ||||
| 189 | + | #### Dockerfile VOLUME (Automatic Detection) | |||
| 190 | + | ```dockerfile | |||
| 191 | + | FROM postgres:15 | |||
| 192 | + | # Gordon automatically creates persistent volumes for these paths | |||
| 193 | + | VOLUME ["/var/lib/postgresql/data"] | |||
| 194 | + | ||||
| 195 | + | # Multi-volume example | |||
| 196 | + | FROM myapp:latest | |||
| 197 | + | VOLUME ["/app/data", "/app/logs", "/app/uploads"] | |||
| 198 | + | ``` | |||
| 199 | + | ||||
| 200 | + | #### Volume Features | |||
| 201 | + | - **Zero Configuration**: Reads VOLUME directives automatically | |||
| 202 | + | - **Persistent Storage**: Data survives container restarts and updates | |||
| 203 | + | - **Predictable Naming**: `gordon-{domain}-{path-hash}` format | |||
| 204 | + | - **Cross-Platform**: Works with Docker and Podman | |||
| 205 | + | ||||
| 206 | + | #### Volume Configuration (Optional) | |||
| 207 | + | ```toml | |||
| 208 | + | [volumes] | |||
| 209 | + | auto_create = true # Default: true (automatically handle VOLUME directives) | |||
| 210 | + | prefix = "gordon" # Default: "gordon" (volume name prefix) | |||
| 211 | + | preserve = true # Default: true (keep volumes when containers are removed) | |||
| 212 | + | ``` | |||
| 213 | + | ||||
| 214 | + | #### Complete Dockerfile Example | |||
| 215 | + | ```dockerfile | |||
| 216 | + | FROM node:18-alpine | |||
| 217 | + | WORKDIR /app | |||
| 218 | + | ||||
| 219 | + | # Environment variables (automatically merged with your .env files) | |||
| 220 | + | ENV NODE_ENV=production | |||
| 221 | + | ENV PORT=3000 | |||
| 222 | + | ENV LOG_LEVEL=info | |||
| 223 | + | ||||
| 224 | + | # Install dependencies | |||
| 225 | + | COPY package*.json ./ | |||
| 226 | + | RUN npm install | |||
| 227 | + | ||||
| 228 | + | # Copy application code | |||
| 229 | + | COPY . . | |||
| 230 | + | ||||
| 231 | + | # Persistent storage (automatically managed by Gordon) | |||
| 232 | + | VOLUME ["/app/data", "/app/logs"] | |||
| 233 | + | ||||
| 234 | + | # Network configuration | |||
| 235 | + | EXPOSE 3000 | |||
| 236 | + | ||||
| 237 | + | # Start command | |||
| 238 | + | CMD ["npm", "start"] | |||
| 239 | + | ``` | |||
| 240 | + | ||||
| 241 | + | When you deploy this image: | |||
| 242 | + | - Gordon reads ENV directives and merges with your .env files | |||
| 243 | + | - Gordon creates volumes for `/app/data` and `/app/logs` | |||
| 244 | + | - Your data persists across deployments and reboots | |||
| 245 | + | - Environment variables are intelligently managed | |||
| 246 | + | ||||
| 139 | ## 🌐 DNS Configuration | 247 | ## 🌐 DNS Configuration | ||
| 140 | 248 | ||||
| 141 | ### Cloudflare Setup (Recommended) | 249 | ### Cloudflare Setup (Recommended) | ||
M examples/development.toml +18 -0
| @@ -94,4 +94,22 @@ providers = ["pass"] # For development secrets | |||
| 94 | 94 | # VOLUME ["/app/data", "/app/uploads"] | |
| 95 | 95 | # Gordon automatically creates and mounts persistent volumes - no config needed! | |
| 96 | 96 | # | |
| 97 | + | # ENV DIRECTIVE FEATURE: | |
| 98 | + | # Add ENV directives to your Dockerfile for default environment variables: | |
| 99 | + | # ENV NODE_ENV=production | |
| 100 | + | # ENV PORT=3000 | |
| 101 | + | # ENV LOG_LEVEL=info | |
| 102 | + | # Gordon automatically reads these and merges them with your .env files! | |
| 103 | + | # Your .env files always override Dockerfile ENV directives. | |
| 104 | + | # | |
| 105 | + | # Example Dockerfile with both features: | |
| 106 | + | # FROM node:18-alpine | |
| 107 | + | # WORKDIR /app | |
| 108 | + | # ENV NODE_ENV=production | |
| 109 | + | # ENV PORT=3000 | |
| 110 | + | # VOLUME ["/app/data", "/app/logs"] | |
| 111 | + | # COPY . . | |
| 112 | + | # EXPOSE 3000 | |
| 113 | + | # CMD ["npm", "start"] | |
| 114 | + | # | |
| 97 | 115 | # Cloudflare handles SSL and DNS for your remote Gordon server!⏎̸ | |
| @@ -94,4 +94,22 @@ providers = ["pass"] # For development secrets | |||||
| 94 | # VOLUME ["/app/data", "/app/uploads"] | 94 | # VOLUME ["/app/data", "/app/uploads"] | ||
| 95 | # Gordon automatically creates and mounts persistent volumes - no config needed! | 95 | # Gordon automatically creates and mounts persistent volumes - no config needed! | ||
| 96 | # | 96 | # | ||
| 97 | + | # ENV DIRECTIVE FEATURE: | |||
| 98 | + | # Add ENV directives to your Dockerfile for default environment variables: | |||
| 99 | + | # ENV NODE_ENV=production | |||
| 100 | + | # ENV PORT=3000 | |||
| 101 | + | # ENV LOG_LEVEL=info | |||
| 102 | + | # Gordon automatically reads these and merges them with your .env files! | |||
| 103 | + | # Your .env files always override Dockerfile ENV directives. | |||
| 104 | + | # | |||
| 105 | + | # Example Dockerfile with both features: | |||
| 106 | + | # FROM node:18-alpine | |||
| 107 | + | # WORKDIR /app | |||
| 108 | + | # ENV NODE_ENV=production | |||
| 109 | + | # ENV PORT=3000 | |||
| 110 | + | # VOLUME ["/app/data", "/app/logs"] | |||
| 111 | + | # COPY . . | |||
| 112 | + | # EXPOSE 3000 | |||
| 113 | + | # CMD ["npm", "start"] | |||
| 114 | + | # | |||
| 97 | # Cloudflare handles SSL and DNS for your remote Gordon server!⏎̸ | 115 | # Cloudflare handles SSL and DNS for your remote Gordon server!⏎̸ | ||
M internal/container/docker.go +21 -0
| @@ -529,3 +529,24 @@ func (d *DockerRuntime) RemoveVolume(ctx context.Context, volumeName string, for | |||
| 529 | 529 | log.Info().Str("volume", volumeName).Msg("Volume removed") | |
| 530 | 530 | return nil | |
| 531 | 531 | } | |
| 532 | + | ||
| 533 | + | // InspectImageEnv gets the environment variables declared in the image | |
| 534 | + | func (d *DockerRuntime) InspectImageEnv(ctx context.Context, imageRef string) ([]string, error) { | |
| 535 | + | imageInspect, err := d.client.ImageInspect(ctx, imageRef) | |
| 536 | + | if err != nil { | |
| 537 | + | return nil, fmt.Errorf("failed to inspect image %s: %w", imageRef, err) | |
| 538 | + | } | |
| 539 | + | ||
| 540 | + | var envVars []string | |
| 541 | + | if imageInspect.Config != nil && imageInspect.Config.Env != nil { | |
| 542 | + | envVars = imageInspect.Config.Env | |
| 543 | + | } | |
| 544 | + | ||
| 545 | + | if len(envVars) > 0 { | |
| 546 | + | log.Debug().Str("image", imageRef).Strs("env_vars", envVars).Msg("Found ENV directives in image") | |
| 547 | + | } else { | |
| 548 | + | log.Debug().Str("image", imageRef).Msg("No ENV directives found in image") | |
| 549 | + | } | |
| 550 | + | ||
| 551 | + | return envVars, nil | |
| 552 | + | } | |
| @@ -529,3 +529,24 @@ func (d *DockerRuntime) RemoveVolume(ctx context.Context, volumeName string, for | |||||
| 529 | log.Info().Str("volume", volumeName).Msg("Volume removed") | 529 | log.Info().Str("volume", volumeName).Msg("Volume removed") | ||
| 530 | return nil | 530 | return nil | ||
| 531 | } | 531 | } | ||
| 532 | + | ||||
| 533 | + | // InspectImageEnv gets the environment variables declared in the image | |||
| 534 | + | func (d *DockerRuntime) InspectImageEnv(ctx context.Context, imageRef string) ([]string, error) { | |||
| 535 | + | imageInspect, err := d.client.ImageInspect(ctx, imageRef) | |||
| 536 | + | if err != nil { | |||
| 537 | + | return nil, fmt.Errorf("failed to inspect image %s: %w", imageRef, err) | |||
| 538 | + | } | |||
| 539 | + | ||||
| 540 | + | var envVars []string | |||
| 541 | + | if imageInspect.Config != nil && imageInspect.Config.Env != nil { | |||
| 542 | + | envVars = imageInspect.Config.Env | |||
| 543 | + | } | |||
| 544 | + | ||||
| 545 | + | if len(envVars) > 0 { | |||
| 546 | + | log.Debug().Str("image", imageRef).Strs("env_vars", envVars).Msg("Found ENV directives in image") | |||
| 547 | + | } else { | |||
| 548 | + | log.Debug().Str("image", imageRef).Msg("No ENV directives found in image") | |||
| 549 | + | } | |||
| 550 | + | ||||
| 551 | + | return envVars, nil | |||
| 552 | + | } | |||
M internal/container/helpers.go +31 -0
| @@ -54,3 +54,34 @@ func normalizeImageRef(imageRef string) string { | |||
| 54 | 54 | ||
| 55 | 55 | return image + ":" + tag | |
| 56 | 56 | } | |
| 57 | + | ||
| 58 | + | // mergeEnvironmentVariables merges Dockerfile ENV directives with user-provided env vars | |
| 59 | + | // User-provided env vars take precedence over Dockerfile ENV directives | |
| 60 | + | func mergeEnvironmentVariables(dockerfileEnv, userEnv []string) []string { | |
| 61 | + | // Create a map to store environment variables by key | |
| 62 | + | envMap := make(map[string]string) | |
| 63 | + | ||
| 64 | + | // First, add Dockerfile ENV directives | |
| 65 | + | for _, env := range dockerfileEnv { | |
| 66 | + | parts := strings.SplitN(env, "=", 2) | |
| 67 | + | if len(parts) == 2 { | |
| 68 | + | envMap[parts[0]] = parts[1] | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | ||
| 72 | + | // Then, add user-provided env vars (these override Dockerfile ENV) | |
| 73 | + | for _, env := range userEnv { | |
| 74 | + | parts := strings.SplitN(env, "=", 2) | |
| 75 | + | if len(parts) == 2 { | |
| 76 | + | envMap[parts[0]] = parts[1] | |
| 77 | + | } | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | // Convert back to slice format | |
| 81 | + | var result []string | |
| 82 | + | for key, value := range envMap { | |
| 83 | + | result = append(result, fmt.Sprintf("%s=%s", key, value)) | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | return result | |
| 87 | + | } | |
| @@ -54,3 +54,34 @@ func normalizeImageRef(imageRef string) string { | |||||
| 54 | 54 | ||||
| 55 | return image + ":" + tag | 55 | return image + ":" + tag | ||
| 56 | } | 56 | } | ||
| 57 | + | ||||
| 58 | + | // mergeEnvironmentVariables merges Dockerfile ENV directives with user-provided env vars | |||
| 59 | + | // User-provided env vars take precedence over Dockerfile ENV directives | |||
| 60 | + | func mergeEnvironmentVariables(dockerfileEnv, userEnv []string) []string { | |||
| 61 | + | // Create a map to store environment variables by key | |||
| 62 | + | envMap := make(map[string]string) | |||
| 63 | + | ||||
| 64 | + | // First, add Dockerfile ENV directives | |||
| 65 | + | for _, env := range dockerfileEnv { | |||
| 66 | + | parts := strings.SplitN(env, "=", 2) | |||
| 67 | + | if len(parts) == 2 { | |||
| 68 | + | envMap[parts[0]] = parts[1] | |||
| 69 | + | } | |||
| 70 | + | } | |||
| 71 | + | ||||
| 72 | + | // Then, add user-provided env vars (these override Dockerfile ENV) | |||
| 73 | + | for _, env := range userEnv { | |||
| 74 | + | parts := strings.SplitN(env, "=", 2) | |||
| 75 | + | if len(parts) == 2 { | |||
| 76 | + | envMap[parts[0]] = parts[1] | |||
| 77 | + | } | |||
| 78 | + | } | |||
| 79 | + | ||||
| 80 | + | // Convert back to slice format | |||
| 81 | + | var result []string | |||
| 82 | + | for key, value := range envMap { | |||
| 83 | + | result = append(result, fmt.Sprintf("%s=%s", key, value)) | |||
| 84 | + | } | |||
| 85 | + | ||||
| 86 | + | return result | |||
| 87 | + | } | |||
M internal/container/manager.go +13 -1
| @@ -216,12 +216,24 @@ func (m *Manager) DeployContainer(ctx context.Context, route config.Route) (*run | |||
| 216 | 216 | } | |
| 217 | 217 | ||
| 218 | 218 | // Load environment variables for this route | |
| 219 | - | envVars, err := m.envLoader.LoadEnvForRoute(route.Domain) | |
| 219 | + | userEnvVars, err := m.envLoader.LoadEnvForRoute(route.Domain) | |
| 220 | 220 | if err != nil { | |
| 221 | 221 | log.Error().Err(err).Str("domain", route.Domain).Msg("Failed to load environment variables for route") | |
| 222 | 222 | return nil, fmt.Errorf("failed to load environment variables for %s: %w", route.Domain, err) | |
| 223 | 223 | } | |
| 224 | 224 | ||
| 225 | + | // Get environment variables from image ENV directives | |
| 226 | + | dockerfileEnvVars, err := m.runtime.InspectImageEnv(ctx, imageRef) | |
| 227 | + | if err != nil { | |
| 228 | + | log.Warn().Err(err).Str("image", imageRef).Msg("Failed to inspect image environment variables, proceeding without them") | |
| 229 | + | dockerfileEnvVars = []string{} | |
| 230 | + | } else if len(dockerfileEnvVars) > 0 { | |
| 231 | + | log.Info().Str("image", imageRef).Strs("dockerfile_env", dockerfileEnvVars).Msg("Found ENV directives in image") | |
| 232 | + | } | |
| 233 | + | ||
| 234 | + | // Merge Dockerfile ENV with user-provided env vars (user env takes precedence) | |
| 235 | + | envVars := mergeEnvironmentVariables(dockerfileEnvVars, userEnvVars) | |
| 236 | + | ||
| 225 | 237 | // Handle volume auto-creation if enabled | |
| 226 | 238 | volumes := make(map[string]string) | |
| 227 | 239 | if m.config.Volumes.AutoCreate { | |
| @@ -216,12 +216,24 @@ func (m *Manager) DeployContainer(ctx context.Context, route config.Route) (*run | |||||
| 216 | } | 216 | } | ||
| 217 | 217 | ||||
| 218 | // Load environment variables for this route | 218 | // Load environment variables for this route | ||
| 219 | - | envVars, err := m.envLoader.LoadEnvForRoute(route.Domain) | 219 | + | userEnvVars, err := m.envLoader.LoadEnvForRoute(route.Domain) |
| 220 | if err != nil { | 220 | if err != nil { | ||
| 221 | log.Error().Err(err).Str("domain", route.Domain).Msg("Failed to load environment variables for route") | 221 | log.Error().Err(err).Str("domain", route.Domain).Msg("Failed to load environment variables for route") | ||
| 222 | return nil, fmt.Errorf("failed to load environment variables for %s: %w", route.Domain, err) | 222 | return nil, fmt.Errorf("failed to load environment variables for %s: %w", route.Domain, err) | ||
| 223 | } | 223 | } | ||
| 224 | 224 | ||||
| 225 | + | // Get environment variables from image ENV directives | |||
| 226 | + | dockerfileEnvVars, err := m.runtime.InspectImageEnv(ctx, imageRef) | |||
| 227 | + | if err != nil { | |||
| 228 | + | log.Warn().Err(err).Str("image", imageRef).Msg("Failed to inspect image environment variables, proceeding without them") | |||
| 229 | + | dockerfileEnvVars = []string{} | |||
| 230 | + | } else if len(dockerfileEnvVars) > 0 { | |||
| 231 | + | log.Info().Str("image", imageRef).Strs("dockerfile_env", dockerfileEnvVars).Msg("Found ENV directives in image") | |||
| 232 | + | } | |||
| 233 | + | ||||
| 234 | + | // Merge Dockerfile ENV with user-provided env vars (user env takes precedence) | |||
| 235 | + | envVars := mergeEnvironmentVariables(dockerfileEnvVars, userEnvVars) | |||
| 236 | + | ||||
| 225 | // Handle volume auto-creation if enabled | 237 | // Handle volume auto-creation if enabled | ||
| 226 | volumes := make(map[string]string) | 238 | volumes := make(map[string]string) | ||
| 227 | if m.config.Volumes.AutoCreate { | 239 | if m.config.Volumes.AutoCreate { | ||
M pkg/runtime/interface.go +3 -0
| @@ -66,4 +66,7 @@ type Runtime interface { | |||
| 66 | 66 | VolumeExists(ctx context.Context, volumeName string) (bool, error) | |
| 67 | 67 | CreateVolume(ctx context.Context, volumeName string) error | |
| 68 | 68 | RemoveVolume(ctx context.Context, volumeName string, force bool) error | |
| 69 | + | ||
| 70 | + | // Environment inspection | |
| 71 | + | InspectImageEnv(ctx context.Context, imageRef string) ([]string, error) | |
| 69 | 72 | }⏎̸ | |
| @@ -66,4 +66,7 @@ type Runtime interface { | |||||
| 66 | VolumeExists(ctx context.Context, volumeName string) (bool, error) | 66 | VolumeExists(ctx context.Context, volumeName string) (bool, error) | ||
| 67 | CreateVolume(ctx context.Context, volumeName string) error | 67 | CreateVolume(ctx context.Context, volumeName string) error | ||
| 68 | RemoveVolume(ctx context.Context, volumeName string, force bool) error | 68 | RemoveVolume(ctx context.Context, volumeName string, force bool) error | ||
| 69 | + | ||||
| 70 | + | // Environment inspection | |||
| 71 | + | InspectImageEnv(ctx context.Context, imageRef string) ([]string, error) | |||
| 69 | }⏎̸ | 72 | }⏎̸ | ||