Skip to content

fix: deploy on Dokploy from .env settings with one lean image - #53

Merged
voidash merged 7 commits into
mainfrom
chore/lean-runtime-image
Oct 4, 2026
Merged

voidash merged 7 commits into
mainfrom
chore/lean-runtime-image

Conversation

@voidash

@voidash voidash commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

Makes the Docker deployment work from the settings a hosting platform provides, and ships one 286 MB image instead of a 341 MB app image plus a 2.07 GB migration image.

  • Settings from .env. Compose required apps/api/.env.local and passed nothing else into the containers. A deployment that supplies settings the usual way, in a .env next to docker-compose.yml, could not start: Compose refused to run without the file, and once it existed the app received only DATABASE_URL and STORAGE_DIR, so it stopped with "Invalid environment configuration". Compose now reads every setting from one file, the .env next to docker-compose.yml: both its own settings and the ones it passes to the app and migrate. apps/api/.env.local is only for bun run dev. The Compose-only settings (POSTGRES_*, DB_PORT, API_PORT) are listed in a section of apps/api/.env.example, which Docker users copy to .env.

  • Production stack for Dokploy. prod-docker-compose.yaml runs the one-shot migration and the app against the project's Dokploy-managed Postgres (DATABASE_URL = its Internal Connection URL, reached over dokploy-network), so the database gets Dokploy's database backups. Settings load from the .env Dokploy writes; the app only exposes 3000; avatars are in a named volume; no container_name. Compose refuses to deploy without DATABASE_URL or AUTH_URL. docs/deployment.md has step-by-step Dokploy instructions.

  • Database credentials from .env. POSTGRES_USER, POSTGRES_PASSWORD and POSTGRES_DB can be set there (defaults unchanged), and DATABASE_URL is built from them.

  • No separate migration image. The migration, project-init and GitHub-sync scripts are bundled with bun build into three single files (1.1 MB in total) that plain Node runs from the app image. The migrate service in Compose uses the same image with node scripts/migrate.js, and reads the env file so one-off tasks get GITHUB_TOKEN.

  • One migrator everywhere. db:migrate uses Drizzle's built-in migrator, the one the tests already used, instead of drizzle-kit migrate. Both record applied migrations in the same table, so existing databases carry on as they are.

  • No unused image optimiser. sharp and libvips (about 19 MB) are left out of the standalone output; no page uses next/image, and images.unoptimized is set.

  • No package managers (npm, yarn, corepack) in the runtime image.

  • Base images pinned by digest.

  • Configurable ports. DB_PORT and API_PORT override the host ports in compose.yaml (defaults unchanged: 5432, 3000), so the stack can run beside another Postgres or app. This was chore: make compose ports configurable #38, folded in here.

Closes #52

Contract impact

  • No changes to packages/api-contract

Checks

  • bun run lint
  • bun run typecheck
  • bun run test (150 passed)
  • bun run build
  • CI's migration step (bun run db:migrate on a clean database) with the new script

Tested with Docker from a clone of this branch:

Check Result
Image 286 MB (was 341 MB + 2.07 GB); no npm/yarn/corepack, no sharp; runs as node
docker compose up on a fresh database migrations applied, app healthy in 27 s
node scripts/init-project.js, node scripts/sync-github.js in the image project initialised, issues synced
Pages and API (/en, /ne, members, issues, project, /v1/*, /health) 200; HTML byte-for-byte the same size as from the old image
Browser: static assets all 10 same-origin images, fonts and stylesheets load
DB_PORT=15432 API_PORT=13000 docker compose up -d beside a stack holding 5432 and 3000 runs; defaults unchanged
Platform style: only a root .env (custom POSTGRES_PASSWORD, AUTH_URL), no .env.local settings reach the app, migrations run, app healthy, callback is https://devnepal.gov.np/..., pages 200
prod-docker-compose.yaml without DATABASE_URL or AUTH_URL refuses to deploy, naming the missing setting
prod-docker-compose.yaml against a stand-in for the Dokploy Postgres service on dokploy-network migrations applied to it, app healthy, callback https://devnepal.gov.np/..., pages 200, node scripts/init-project.js works, no host ports
Only apps/api/.env.local Compose ignores it and uses the defaults; docker compose up -d db (used by bun run setup) still starts
Both files only .env is used, for the containers and for POSTGRES_*/ports alike
drizzle-kit migrate then this image's migration on the same database nothing applied twice (3 migration rows)
New migrator then drizzle-kit migrate, and the reverse, on fresh databases identical migration history and tables

Notes for reviewers

outputFileTracingExcludes names Bun's node_modules/.bun/ layout explicitly, because globs do not match that hidden directory. If the package manager changes, those two paths need updating; the build would then include sharp again, not break.

Foundation checklist

  • Commits signed off (git commit -s)
  • Works in both English and Nepali where applicable (no text changes)
  • No secrets or real personal data anywhere in the diff
  • Documentation updated (docs/deployment.md, .env.example)

@voidash
voidash changed the base branch from chore/configurable-compose-ports to main September 28, 2026 06:27
@voidash voidash changed the title chore: ship one lean runtime image chore: ship one lean runtime image with configurable ports Sep 28, 2026
@abhiyandhakal

Copy link
Copy Markdown
Collaborator

@voidash seems like there is a merge conflict.

The host ports for Postgres and the app were fixed at 5432 and 3000.
They can now be overridden with DB_PORT and API_PORT; the defaults are
unchanged. Also document that the migrate image runs one-off tasks such
as db:init, and fix a Dockerfile comment that still mentioned the
removed design CSS and fonts.

Signed-off-by: voidash <ashish.thapa477@gmail.com>
Migrations ran from a separate image that was the whole build stage,
dev dependencies included (2.07 GB), as root. The migration, project-init
and GitHub-sync scripts are now bundled into single files that Node runs
from the app image, so the migrate service uses the same image and the
separate one is gone. db:migrate uses the same Drizzle migrator, which
shares its history table with drizzle-kit.

The runtime image also leaves out the unused image optimiser (sharp and
libvips, about 19 MB; no page uses next/image) and the package managers,
and both base images are pinned by digest.

Signed-off-by: voidash <ashish.thapa477@gmail.com>
Compose required apps/api/.env.local and passed nothing else into the
containers, so a deployment that supplies settings the usual way, in a
.env next to docker-compose.yml, could not start: Compose refused to run
without the file, and once it existed the app still received only
DATABASE_URL and STORAGE_DIR. Both files are now optional env files for
the app and migrate services; .env.local wins where both set a value.

The Postgres credentials can also be set from .env (POSTGRES_USER,
POSTGRES_PASSWORD, POSTGRES_DB), with the previous values as defaults,
and DATABASE_URL is built from them.

Signed-off-by: voidash <ashish.thapa477@gmail.com>
@voidash
voidash force-pushed the chore/lean-runtime-image branch from 790297a to b732659 Compare September 30, 2026 18:46
@voidash voidash changed the title chore: ship one lean runtime image with configurable ports fix: deploy from .env settings with one lean image Sep 30, 2026
prod-docker-compose.yaml runs Postgres, the one-shot migration and the
app the way Dokploy expects: settings load from the .env Dokploy writes,
the app only exposes port 3000 and joins dokploy-network for Traefik,
Postgres stays on the stack's own network, data is in named volumes,
and no service sets container_name. Compose refuses to deploy without
AUTH_URL or POSTGRES_PASSWORD, and there is no default database
password. The deployment guide gets step-by-step Dokploy instructions.

Signed-off-by: voidash <ashish.thapa477@gmail.com>
@voidash voidash changed the title fix: deploy from .env settings with one lean image fix: deploy on Dokploy from .env settings with one lean image Oct 3, 2026
Comment thread docker-compose.yml
Compose substitutes ${...} only from the shell and the .env next to
docker-compose.yml, so POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB,
DB_PORT and API_PORT set in apps/api/.env.local were silently ignored.
The compose header implied otherwise. Say which settings come from where,
add a root .env.example for the Compose-level ones, and note it in the
deployment guide.

Signed-off-by: voidash <ashish.thapa477@gmail.com>
Compose read its own settings (POSTGRES_*, DB_PORT, API_PORT) only from
.env, while the containers also took apps/api/.env.local and let it win,
so some settings could be overridden from .env.local and others not.
Compose now reads only .env, for both; apps/api/.env.local is for
'bun run dev' alone. The Compose-only settings move into a section of
apps/api/.env.example, replacing the root .env.example.

Signed-off-by: voidash <ashish.thapa477@gmail.com>
abhiyandhakal
abhiyandhakal previously approved these changes Oct 4, 2026
The Dokploy project already runs a managed Postgres service, which gets
Dokploy's database backups. prod-docker-compose.yaml no longer bundles
its own database: the app and migrate read DATABASE_URL (the service's
Internal Connection URL) and reach it over dokploy-network. Compose
refuses to deploy without DATABASE_URL or AUTH_URL. The Dokploy steps in
the deployment guide are updated to match.

Signed-off-by: voidash <ashish.thapa477@gmail.com>
@voidash
voidash merged commit 4ce12d6 into main Oct 4, 2026
1 check passed
@voidash
voidash deleted the chore/lean-runtime-image branch October 4, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compose deployment cannot use platform settings and needs a 2 GB migration image

2 participants