detached d68e909

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
d68e909b0860f437d3692b0818dc1b45b996d6f1
author
bamen · · 2025-06-08 14:59 UTC
parent
a1b18e2
signature
unsigned
7 files · +224 -2
M README.md +29 -0
@@ -43,6 +43,7 @@ podman push registry.mydomain.com/myapp:latest
4343 - **Zero-Downtime Updates**: Graceful container swaps
4444 - **Auto-Route Creation** (Optional): Push `myapp.mydomain.com:latest` → route created automatically
4545 - **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
4647
4748 ## Quick Start (5 minutes)
4849
@@ -291,6 +292,22 @@ preserve = true # Default: true (keep volumes when removing containers)
291292
292293 **Most users never need to touch this configuration** - the defaults work perfectly for 99% of use cases.
293294
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+
294311 ## Advanced Configuration
295312
296313 ### Full Config Structure (with default values)
@@ -378,6 +395,18 @@ A: Volumes are preserved by default. Your data persists across updates and reboo
378395 **Q: How do I backup volume data?**
379396 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 .`
380397
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+
381410 ## Philosophy
382411
383412 ```bash
@@ -43,6 +43,7 @@ podman push registry.mydomain.com/myapp:latest
43 - **Zero-Downtime Updates**: Graceful container swaps43 - **Zero-Downtime Updates**: Graceful container swaps
44 - **Auto-Route Creation** (Optional): Push `myapp.mydomain.com:latest` → route created automatically44 - **Auto-Route Creation** (Optional): Push `myapp.mydomain.com:latest` → route created automatically
45 - **Auto-Volume Management**: Zero-config persistent storage from Dockerfile VOLUME directives45 - **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 Configuration311 ## 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 ## Philosophy410 ## Philosophy
382 411
383 ```bash412 ```bash
M examples/README.md +109 -1
@@ -130,12 +130,120 @@ compress = true # Compress old log files
130130 See [`logging.toml`](logging.toml) for comprehensive logging examples.
131131
132132 ### 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:
134181 ```bash
135182 export GORDON_REGISTRY_PASSWORD="your-secure-password"
136183 export GORDON_SSL_EMAIL="admin@your-domain.com"
137184 ```
138185
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+
139247 ## 🌐 DNS Configuration
140248
141249 ### 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 Variables132 ### 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 ```bash181 ```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 Configuration247 ## 🌐 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
9494 # VOLUME ["/app/data", "/app/uploads"]
9595 # Gordon automatically creates and mounts persistent volumes - no config needed!
9696 #
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+#
97115 # 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
529529 log.Info().Str("volume", volumeName).Msg("Volume removed")
530530 return nil
531531 }
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 nil530 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 {
5454
5555 return image + ":" + tag
5656 }
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 + ":" + tag55 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
216216 }
217217
218218 // Load environment variables for this route
219- envVars, err := m.envLoader.LoadEnvForRoute(route.Domain)
219+ userEnvVars, err := m.envLoader.LoadEnvForRoute(route.Domain)
220220 if err != nil {
221221 log.Error().Err(err).Str("domain", route.Domain).Msg("Failed to load environment variables for route")
222222 return nil, fmt.Errorf("failed to load environment variables for %s: %w", route.Domain, err)
223223 }
224224
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+
225237 // Handle volume auto-creation if enabled
226238 volumes := make(map[string]string)
227239 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 route218 // 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 enabled237 // 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 {
6666 VolumeExists(ctx context.Context, volumeName string) (bool, error)
6767 CreateVolume(ctx context.Context, volumeName string) error
6868 RemoveVolume(ctx context.Context, volumeName string, force bool) error
69+
70+ // Environment inspection
71+ InspectImageEnv(ctx context.Context, imageRef string) ([]string, error)
6972 }⏎̸
@@ -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) error67 CreateVolume(ctx context.Context, volumeName string) error
68 RemoveVolume(ctx context.Context, volumeName string, force bool) error68 RemoveVolume(ctx context.Context, volumeName string, force bool) error
69+
70+ // Environment inspection
71+ InspectImageEnv(ctx context.Context, imageRef string) ([]string, error)
69 }⏎̸72 }⏎̸

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close