🧠 Context
The bot image is about 570 MB and a large chunk of that is dev tooling the running bot never uses. The build in Dockerfile runs:
RUN uv sync --frozen --no-install-project
with no --no-dev, so it installs the whole [dependency-groups] dev set from pyproject.toml — pytest, pytest-asyncio, pytest-cov, ruff, black, pre-commit, psycopg2-binary — into what should be a runtime-only image. The bot imports and runs with only the runtime dependencies, so none of that is needed at container run time.
There's a second, connected problem. The image CMD (and the compose bot service's command:) both launch the bot with uv run python -m src.apps.discord_bot. uv run re-syncs against the lockfile at container startup — which re-installs the full dev group again (so --no-dev at build time alone gets undone at boot) and makes the container need PyPI access just to start. So the fix has to change both how the image is built and how it's launched.
Files this ticket owns:
Dockerfile
docker-compose.yml (the bot service's command:)
🛠 Implementation Plan
1. Build without dev dependencies
Add --no-dev to the build sync so only runtime dependencies land in the image:
RUN uv sync --frozen --no-install-project --no-dev
Keep --no-install-project — this is a run-from-source project (no [build-system]), so the project itself isn't installed as a package; the bot runs from the COPYed src/ via -m.
2. Launch the bot directly from the venv, not through uv run
Change the image CMD from uv run python -m ... to run the venv's Python directly:
CMD [".venv/bin/python", "-m", "src.apps.discord_bot"]
This bypasses the startup re-sync entirely: the container boots deterministically and offline, using exactly the dependencies baked in at build time. Without this change, --no-dev from step 1 is pointless — uv run would re-add the dev group on every boot.
3. Fix the compose override too
The bot service in docker-compose.yml sets command: uv run python -m src.apps.discord_bot, which overrides the image CMD. So changing only the Dockerfile leaves the compose-run bot still going through uv run and re-syncing at boot. Either update that command: to match the direct-venv form, or drop the line so the service inherits the fixed CMD from the image. Dropping it is cleaner — one definition instead of two that can drift.
4. Verify the change
Size — build before and after and compare. The absolute size drifts with the base-image version, so measure your own before/after rather than aiming at a fixed number:
docker build -t cs-bot:before . # current Dockerfile, before your edits
# ...apply the changes...
docker build -t cs-bot:after .
docker images cs-bot # compare the SIZE column
A build on current main measures roughly 570 MB before and 475 MB after (~95 MB smaller).
Dev tools are gone — this should raise ModuleNotFoundError:
docker run --rm cs-bot:after .venv/bin/python -c "import pytest"
The bot still launches — bring it up with your real .env and confirm it connects (the discord_ready log line / the bot showing online):
docker compose --profile bot up bot
📝 Notes
.dockerignore already keeps tests/, data/, .venv/, .git/, and .env out of the build context — no change needed there.
✅ Acceptance Criteria
- The image builds with
uv sync --frozen --no-install-project --no-dev; the dev tools (pytest/ruff/black/pre-commit/psycopg2-binary) are no longer in the image.
- The image
CMD and the compose bot command: no longer use uv run — the bot launches via .venv/bin/python -m src.apps.discord_bot (compose either matches or inherits the CMD).
- The container starts without a startup dependency sync and without needing network access to PyPI, and the bot connects to Discord as before (
docker compose --profile bot up works).
- The image is meaningfully smaller — roughly ~475 MB vs ~570 MB on a current build (~95 MB), though the absolute size drifts with the base image.
🧠 Context
The bot image is about 570 MB and a large chunk of that is dev tooling the running bot never uses. The build in
Dockerfileruns:with no
--no-dev, so it installs the whole[dependency-groups] devset frompyproject.toml—pytest,pytest-asyncio,pytest-cov,ruff,black,pre-commit,psycopg2-binary— into what should be a runtime-only image. The bot imports and runs with only the runtime dependencies, so none of that is needed at container run time.There's a second, connected problem. The image
CMD(and the composebotservice'scommand:) both launch the bot withuv run python -m src.apps.discord_bot.uv runre-syncs against the lockfile at container startup — which re-installs the full dev group again (so--no-devat build time alone gets undone at boot) and makes the container need PyPI access just to start. So the fix has to change both how the image is built and how it's launched.Files this ticket owns:
Dockerfiledocker-compose.yml(thebotservice'scommand:)🛠 Implementation Plan
1. Build without dev dependencies
Add
--no-devto the build sync so only runtime dependencies land in the image:Keep
--no-install-project— this is a run-from-source project (no[build-system]), so the project itself isn't installed as a package; the bot runs from the COPYedsrc/via-m.2. Launch the bot directly from the venv, not through
uv runChange the image
CMDfromuv run python -m ...to run the venv's Python directly:This bypasses the startup re-sync entirely: the container boots deterministically and offline, using exactly the dependencies baked in at build time. Without this change,
--no-devfrom step 1 is pointless —uv runwould re-add the dev group on every boot.3. Fix the compose override too
The
botservice indocker-compose.ymlsetscommand: uv run python -m src.apps.discord_bot, which overrides the imageCMD. So changing only the Dockerfile leaves the compose-run bot still going throughuv runand re-syncing at boot. Either update thatcommand:to match the direct-venv form, or drop the line so the service inherits the fixedCMDfrom the image. Dropping it is cleaner — one definition instead of two that can drift.4. Verify the change
Size — build before and after and compare. The absolute size drifts with the base-image version, so measure your own before/after rather than aiming at a fixed number:
A build on current main measures roughly 570 MB before and 475 MB after (~95 MB smaller).
Dev tools are gone — this should raise
ModuleNotFoundError:The bot still launches — bring it up with your real
.envand confirm it connects (thediscord_readylog line / the bot showing online):📝 Notes
.dockerignorealready keepstests/,data/,.venv/,.git/, and.envout of the build context — no change needed there.✅ Acceptance Criteria
uv sync --frozen --no-install-project --no-dev; the dev tools (pytest/ruff/black/pre-commit/psycopg2-binary) are no longer in the image.CMDand the composebotcommand:no longer useuv run— the bot launches via.venv/bin/python -m src.apps.discord_bot(compose either matches or inherits the CMD).docker compose --profile bot upworks).