Ship config as .env.production - Dokploy was overwriting the committed .env
The container was never getting its configuration, so it started with nothing set and Traefik reported a Bad Gateway on every route. Dokploy writes its own .env into the build context from the service's Environment tab AFTER cloning the repository. That tab is empty, so it wrote a zero-byte file over the committed one, and `COPY .env .` faithfully copied the empty result into the image. The checkout showed it exactly: every file timestamped 08:33, and .env alone at 08:34 with a size of 0. Inside the running container, /app/.env was 0 bytes. Nothing about this is visible from the outside. The build log shows the COPY succeeding, the image is produced, and the platform reports only a 502. Dokploy does not manage .env.production, so the config now travels under that name and the Dockerfile copies it to /app/.env in the image. Anything set in the Environment tab still wins at runtime, because settings.py calls load_dotenv() without override=True. Verified by reconstructing the build context the way Dokploy does - git archive of HEAD, then an empty .env written over it - applying .dockerignore and the COPY lines, and booting the result with an empty environment: /app/.env is 4938 bytes, the sign-in passwords are absent, and scripts/check_deploy.sh reports 6 passed, 0 failed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,11 +6,12 @@ __pycache__
|
||||
.git
|
||||
tests
|
||||
|
||||
# .env is deliberately NOT ignored - it carries this deployment's configuration
|
||||
# and the Dockerfile copies it into the image. Excluding it here silently undid
|
||||
# that: the build succeeded, the container started with no configuration at all,
|
||||
# and died on the first required setting.
|
||||
# .env.production carries this deployment's configuration and the Dockerfile
|
||||
# copies it to /app/.env inside the image, so it must NOT be ignored here.
|
||||
# Dokploy's own .env (written from the Environment tab, empty when that tab is
|
||||
# blank) is ignored instead - it is what silently overwrote the committed one.
|
||||
#
|
||||
# The generated sign-in passwords must never be in the image.
|
||||
SIGNIN_PASSWORDS.txt
|
||||
.env
|
||||
.env.local
|
||||
SIGNIN_PASSWORDS.txt
|
||||
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,5 +1,10 @@
|
||||
# .env is committed deliberately, at the repo owner's instruction, so the
|
||||
# deployment does not depend on re-entering config in the Dokploy UI.
|
||||
# .env.production is committed deliberately, at the repo owner's instruction, so
|
||||
# the deployment does not depend on re-entering config in the Dokploy UI.
|
||||
#
|
||||
# It is NOT named .env, and that matters: Dokploy writes its own .env into the
|
||||
# build context from the service's Environment tab after cloning, so a committed
|
||||
# .env is silently replaced (with an empty file when that tab is blank).
|
||||
# The Dockerfile copies .env.production to /app/.env inside the image.
|
||||
#
|
||||
# The two database secrets are NOT in it - they are set as Dokploy environment
|
||||
# variables, which override the file (settings.py calls load_dotenv() without
|
||||
@@ -9,7 +14,8 @@
|
||||
# anyone with read access to this repository can mint an admin token, and git
|
||||
# history keeps it after any rotation. Regenerate with
|
||||
# `python scripts/make_auth_secrets.py` if that stops being acceptable.
|
||||
!.env
|
||||
!.env.production
|
||||
.env
|
||||
|
||||
# Local overrides and the generated sign-in passwords stay out of git.
|
||||
.env.local
|
||||
|
||||
24
Dockerfile
24
Dockerfile
@@ -46,18 +46,22 @@ COPY scripts ./scripts
|
||||
COPY data ./data
|
||||
COPY serve.py .
|
||||
|
||||
# The deployment's configuration. settings.py loads it from the backend root -
|
||||
# app/infrastructure/settings.py resolves parents[2], which is /app here - so it
|
||||
# has to land next to app/, not inside it.
|
||||
# The deployment's configuration, landing at /app/.env because settings.py
|
||||
# resolves it from the backend root - app/infrastructure/settings.py takes
|
||||
# parents[2], which is /app here - so it must sit next to app/, not inside it.
|
||||
#
|
||||
# Easy to leave out, and silent when you do: the image builds, the container
|
||||
# starts with no configuration, and exits on the first required setting with the
|
||||
# platform reporting only a Bad Gateway. .dockerignore must not exclude it
|
||||
# either; that alone is enough to reproduce the same failure.
|
||||
# The source file is named .env.production, NOT .env, and that detail is the
|
||||
# whole point. Dokploy writes its own .env into the build context from the
|
||||
# service's Environment tab AFTER cloning the repository. With that tab empty it
|
||||
# writes an empty file, overwriting the committed one - so `COPY .env .` copied
|
||||
# a zero-byte file, the container started with no configuration at all, and the
|
||||
# platform reported only a Bad Gateway. The checkout showed it plainly: every
|
||||
# file timestamped 08:33, and .env alone at 08:34, 0 bytes.
|
||||
#
|
||||
# Real environment variables still win over this file (load_dotenv is called
|
||||
# without override=True), so Dokploy can override any value without a rebuild.
|
||||
COPY .env .
|
||||
# Dokploy does not manage .env.production, so it survives. Anything set in the
|
||||
# Environment tab still wins at runtime, because settings.py calls load_dotenv()
|
||||
# without override=True and the process environment takes precedence.
|
||||
COPY .env.production .env
|
||||
|
||||
# Pristine copies of everything the app also WRITES to, kept at a path that is
|
||||
# never mounted over.
|
||||
|
||||
Reference in New Issue
Block a user