@@ -66,12 +66,147 @@ docker compose exec phpfpm bin/console app:utils:convert-config-json-to-env --ty
6666
6767#### 2.1 - Rename environment variables
6868
69- Rename the following .env variables in ` .env.local ` :
69+ In 3.x the compose stack carries no ` APP_* ` prefix translation — variable
70+ names in ` .env.local ` must match the Symfony names exactly. Every
71+ ` APP_X ` variable from the previous 2.x ` .env.docker.local ` is renamed
72+ to ` X ` , with the ** exception of ` APP_ENV ` and ` APP_SECRET ` ** which are
73+ Symfony-defined and keep their prefix.
74+
75+ The full rename list:
76+
77+ ``` text
78+ APP_TRUSTED_PROXIES → TRUSTED_PROXIES
79+ APP_DATABASE_URL → DATABASE_URL
80+ APP_CORS_ALLOW_ORIGIN → CORS_ALLOW_ORIGIN
81+ APP_DEFAULT_DATE_FORMAT → DEFAULT_DATE_FORMAT
82+ APP_ACTIVATION_CODE_EXPIRE_INTERVAL → ACTIVATION_CODE_EXPIRE_INTERVAL
83+ APP_KEY_VAULT_SOURCE → KEY_VAULT_SOURCE
84+ APP_KEY_VAULT_JSON → KEY_VAULT_JSON
85+ APP_TRACK_SCREEN_INFO → TRACK_SCREEN_INFO
86+ APP_TRACK_SCREEN_INFO_UPDATE_INTERVAL_SECONDS → TRACK_SCREEN_INFO_UPDATE_INTERVAL_SECONDS
87+
88+ APP_JWT_SECRET_KEY → JWT_SECRET_KEY
89+ APP_JWT_PUBLIC_KEY → JWT_PUBLIC_KEY
90+ APP_JWT_PASSPHRASE → JWT_PASSPHRASE
91+ APP_JWT_TOKEN_TTL → JWT_TOKEN_TTL
92+ APP_JWT_SCREEN_TOKEN_TTL → JWT_SCREEN_TOKEN_TTL
93+ APP_JWT_REFRESH_TOKEN_TTL → JWT_REFRESH_TOKEN_TTL
94+ APP_JWT_SCREEN_REFRESH_TOKEN_TTL → JWT_SCREEN_REFRESH_TOKEN_TTL
95+
96+ APP_REDIS_CACHE_PREFIX → REDIS_CACHE_PREFIX
97+ APP_REDIS_CACHE_DSN → REDIS_CACHE_DSN
98+
99+ APP_HTTP_CLIENT_TIMEOUT → HTTP_CLIENT_TIMEOUT
100+ APP_HTTP_CLIENT_MAX_DURATION → HTTP_CLIENT_MAX_DURATION
101+ APP_HTTP_CLIENT_LOG_LEVEL → HTTP_CLIENT_LOG_LEVEL
102+
103+ APP_INTERNAL_OIDC_METADATA_URL → INTERNAL_OIDC_METADATA_URL
104+ APP_INTERNAL_OIDC_CLIENT_ID → INTERNAL_OIDC_CLIENT_ID
105+ APP_INTERNAL_OIDC_CLIENT_SECRET → INTERNAL_OIDC_CLIENT_SECRET
106+ APP_INTERNAL_OIDC_REDIRECT_URI → INTERNAL_OIDC_REDIRECT_URI
107+ APP_INTERNAL_OIDC_LEEWAY → INTERNAL_OIDC_LEEWAY
108+ APP_INTERNAL_OIDC_CLAIM_NAME → INTERNAL_OIDC_CLAIM_NAME
109+ APP_INTERNAL_OIDC_CLAIM_EMAIL → INTERNAL_OIDC_CLAIM_EMAIL
110+ APP_INTERNAL_OIDC_CLAIM_GROUPS → INTERNAL_OIDC_CLAIM_GROUPS
111+
112+ APP_EXTERNAL_OIDC_METADATA_URL → EXTERNAL_OIDC_METADATA_URL
113+ APP_EXTERNAL_OIDC_CLIENT_ID → EXTERNAL_OIDC_CLIENT_ID
114+ APP_EXTERNAL_OIDC_CLIENT_SECRET → EXTERNAL_OIDC_CLIENT_SECRET
115+ APP_EXTERNAL_OIDC_REDIRECT_URI → EXTERNAL_OIDC_REDIRECT_URI
116+ APP_EXTERNAL_OIDC_LEEWAY → EXTERNAL_OIDC_LEEWAY
117+ APP_EXTERNAL_OIDC_HASH_SALT → EXTERNAL_OIDC_HASH_SALT
118+ APP_EXTERNAL_OIDC_CLAIM_ID → EXTERNAL_OIDC_CLAIM_ID
119+ APP_OIDC_CLI_REDIRECT → OIDC_CLI_REDIRECT
120+
121+ APP_CALENDAR_API_FEED_SOURCE_LOCATION_ENDPOINT → CALENDAR_API_FEED_SOURCE_LOCATION_ENDPOINT
122+ APP_CALENDAR_API_FEED_SOURCE_RESOURCE_ENDPOINT → CALENDAR_API_FEED_SOURCE_RESOURCE_ENDPOINT
123+ APP_CALENDAR_API_FEED_SOURCE_EVENT_ENDPOINT → CALENDAR_API_FEED_SOURCE_EVENT_ENDPOINT
124+ APP_CALENDAR_API_FEED_SOURCE_CUSTOM_MAPPINGS → CALENDAR_API_FEED_SOURCE_CUSTOM_MAPPINGS
125+ APP_CALENDAR_API_FEED_SOURCE_EVENT_MODIFIERS → CALENDAR_API_FEED_SOURCE_EVENT_MODIFIERS
126+ APP_CALENDAR_API_FEED_SOURCE_DATE_FORMAT → CALENDAR_API_FEED_SOURCE_DATE_FORMAT
127+ APP_CALENDAR_API_FEED_SOURCE_DATE_TIMEZONE → CALENDAR_API_FEED_SOURCE_DATE_TIMEZONE
128+ APP_CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS → CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS
129+
130+ APP_EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS → EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS
131+
132+ APP_ADMIN_REJSEPLANEN_APIKEY → ADMIN_REJSEPLANEN_APIKEY
133+ APP_ADMIN_SHOW_SCREEN_STATUS → ADMIN_SHOW_SCREEN_STATUS
134+ APP_ADMIN_TOUCH_BUTTON_REGIONS → ADMIN_TOUCH_BUTTON_REGIONS
135+ APP_ADMIN_LOGIN_METHODS → ADMIN_LOGIN_METHODS
136+ APP_ADMIN_ENHANCED_PREVIEW → ADMIN_ENHANCED_PREVIEW
137+
138+ APP_CLIENT_LOGIN_CHECK_TIMEOUT → CLIENT_LOGIN_CHECK_TIMEOUT
139+ APP_CLIENT_REFRESH_TOKEN_TIMEOUT → CLIENT_REFRESH_TOKEN_TIMEOUT
140+ APP_CLIENT_RELEASE_TIMESTAMP_INTERVAL_TIMEOUT → CLIENT_RELEASE_TIMESTAMP_INTERVAL_TIMEOUT
141+ APP_CLIENT_SCHEDULING_INTERVAL → CLIENT_SCHEDULING_INTERVAL
142+ APP_CLIENT_PULL_STRATEGY_INTERVAL → CLIENT_PULL_STRATEGY_INTERVAL
143+ APP_CLIENT_COLOR_SCHEME → CLIENT_COLOR_SCHEME
144+ APP_CLIENT_DEBUG → CLIENT_DEBUG
145+ ```
146+
147+ The ` os2display-docker-server ` repo provides ` task env:migrate ` , which
148+ performs this rename automatically: it reads a 2.x ` .env.docker.local `
149+ and writes a 3.x-shaped ` .env.local ` . Use it as the recommended
150+ migration path:
151+
152+ ``` shell
153+ # In your os2display-docker-server checkout, on the 3.0 branch:
154+ task env:migrate
155+ task env:diff # review the result against the canonical example
156+ ```
157+
158+ #### 2.2 - Adopt the new operator-facing image-deployment contract
159+
160+ Production deployments now use the GHCR-published images
161+ (` ghcr.io/os2display/display-api-service ` and
162+ ` ghcr.io/os2display/display-api-service-nginx ` ) and follow an ` env_file: `
163+ pattern in the compose stack.
164+
165+ ##### Bootstrap ` .env.local ` from the image
166+
167+ The image ships a self-documenting ` .env ` at ` /var/www/html/.env ` that
168+ lists every Symfony env variable the application consumes, with a
169+ one-line description per variable. Use it as the starting point for
170+ your operator-host ` .env.local ` :
171+
172+ ``` shell
173+ docker run --rm ghcr.io/os2display/display-api-service:< tag> \
174+ cat /var/www/html/.env > .env.local
175+ ```
176+
177+ Then edit ` .env.local ` to set the required values for your environment:
178+
179+ - ` APP_ENV=prod `
180+ - ` APP_SECRET=<generated-secret> `
181+ - ` JWT_PASSPHRASE=<generated-passphrase> `
182+ - ` DATABASE_URL=<connection-string> `
183+ - ` CORS_ALLOW_ORIGIN=<your-allowed-origin-regex> `
184+ - OIDC provider settings (` INTERNAL_OIDC_* ` and/or ` EXTERNAL_OIDC_* ` )
185+
186+ ##### Reference ` .env.local ` from compose via ` env_file: `
187+
188+ ``` yaml
189+ services :
190+ api :
191+ image : ghcr.io/os2display/display-api-service:<tag>
192+ env_file :
193+ - .env.local
194+ ` ` `
195+
196+ The ` os2display-docker-server` compose stack does this for you. See its
197+ ` UPGRADE.md` for full operator migration steps.
198+
199+ # #### nginx and PHP-FPM runtime tuning
200+
201+ OS-level / runtime knobs are independent of the Symfony env surface and
202+ are passed to their respective images via compose `environment:` :
70203
71- - From ` APP_DEFAULT_DATE_FORMAT ` to ` DEFAULT_DATE_FORMAT `
72- - From ` APP_ACTIVATION_CODE_EXPIRE_INTERVAL ` to ` ACTIVATION_CODE_EXPIRE_INTERVAL `
73- - From ` APP_KEY_VAULT_SOURCE ` to ` KEY_VAULT_SOURCE `
74- - From ` APP_KEY_VAULT_JSON ` to ` KEY_VAULT_JSON `
204+ - nginx : ` NGINX_PORT` , `NGINX_FPM_SERVICE`, `NGINX_FPM_PORT`,
205+ ` NGINX_MAX_BODY_SIZE` , `NGINX_SET_REAL_IP_FROM`, `NGINX_WEB_ROOT`
206+ (defaults in `infrastructure/nginx/Dockerfile`).
207+ - PHP-FPM : ` PHP_MEMORY_LIMIT` , `PHP_MAX_EXECUTION_TIME`,
208+ ` PHP_POST_MAX_SIZE` , `PHP_UPLOAD_MAX_FILESIZE`, `PHP_PM_*`,
209+ ` PHP_OPCACHE_*` (consumed by the `itkdev/php8.4-fpm` base image).
75210
76211# ### 3 - Consolidate Doctrine migrations
77212
0 commit comments