docker
Technical notes on web development, DevOps, and AI integration.
35 articles
- 23:02devops
Closing the Door Does Not Hide the Drawers
The deploy port was closed, yet /.git/ stayed readable through the staging hostname. Next: an nginx edge regex guard and an api/db network split.
TL;DR: Closing the inbound port didn't protect a staging server still serving .git and .env files over its tunnel. The fix needs three layers: nginx edge rules denying dot-paths, server_tokens off, and explicit Docker networks isolating services. Each layer covers what the others miss; any one alone leaves the same exposed drawers.
#nginx#docker#devops - 01:42devops
Hybrid Upgrade Playbook: npm In-Container, Image Fallback
A two-phase upgrade playbook: npm inside the running container plus an automatic custom-image fallback, verified against the version the app actually serves.
TL;DR: After upgrading 9router, the endpoint still served the old version because the container ran code baked into the image, not the freshly installed npm package. The rewritten playbook uses a two-phase approach: cheap in-container installs first, then building an image from source, with served-version verification, health checks, and automatic rollback. Running serial: 1 keeps failures contained to one host.
#ansible#docker#npm - 17:45testing
Baseline QA Before Fixes: Red Doesn't Mean Broken Code
The first QA baseline came back all red: a stale database volume and a healthcheck calling a missing wget, not a broken codebase.
TL;DR: Initial integration failures came from environment drift, not broken code, with a stale database volume and a missing wget in the healthcheck. Cleaning volumes with docker compose down -v fixed the setup and let all 28 API packages pass. Coverage gaps and E2E failures were recorded without patching to keep the baseline trustworthy for prioritized follow-ups.
#testing#golang#docker - 23:15backend
One ruff Commit Broke Three Layers at Once
A harmless-looking ruff sweep touched runtime aliases, TYPE_CHECKING boundaries, isort classification, and the Docker build layout all at once.
TL;DR: A harmless-looking lint commit shipped runtime import moves, type-checking guards, and a new pyproject.toml in one package. Only the last piece broke CI, because setuptools' flat-layout discovery choked on the leftover build directory the Docker install created. The fix: exclude build artifacts in ruff config and clean them within the same RUN instruction.
#python#ruff#isort - 17:43devops
Docker Compose Waits for a Ready Database, Not a Running One
depends_on only waits for a running container. The real wait contract is a pg_isready healthcheck, service_healthy, and migrate-before-serve.
TL;DR: Compose's depends_on only guarantees start order, not database readiness, so the app crashed while Postgres was still initializing. The fix pairs a pg_isready healthcheck with depends_on condition: service_healthy, letting the entrypoint run migrations before exec-ing uvicorn so shutdown signals land cleanly. The same pattern works in GitHub Actions CI, and newer Compose versions offer pre_start as an alternative.
#docker#compose#postgresql - 11:32devops
Do Not Use Absolute URLs for Plane Comment Image Embeds
Image embeds in Plane API comments fail hard on absolute URLs (401). Verified pipeline: in-container helper, image-component node, raw asset_id.
TL;DR: Using absolute URLs for Plane comment images always returns 401 because assets must load through an authenticated local proxy. Instead create the comment first, upload via the in-container helper, and embed with the native image-component node using only the raw asset ID. Remember updates replace all HTML so resend full content and verify rendering in the browser not with curl.
#plane#mcp#docker - 08:23devops
Bypassing the MCP Limit: Images in Plane Comments
The Plane MCP server has no upload action and the SSRF guard rightly rejects private URLs. The self-host answer: drop one level and create the FileAsset inside the container.
TL;DR: The Plane MCP server can't upload files and its SSRF guard correctly blocks loopback URLs and internal API keys. The workaround is a tiny Python script that copies the image into the container and uploads it to MinIO via Django shell. Then you patch the comment's HTML with the returned URL, which renders in the UI but stays private.
#plane#mcp#docker - 04:18testing
Unit Testing Whose Contents Are Not Just Unit Tests
One script per test tier: vet, integration against real MySQL, a smoke gate, up to Playwright E2E.
TL;DR: Scattered manual tests were often skipped, so they were consolidated into four strict scripts for backend, frontend, E2E and smoke. Backend handles Docker and runs integration tests serially on real MySQL, while smoke validates deployment endpoints and E2E wraps Playwright with retries. README now defines what to run when, so a passing backend script signals the shared team contract.
#testing#bash#go - 03:24testing
The Category CRUD Was Done. The Tests Did Not Exist
Shipping nested categories with zero tests worked until the QA checklist asked for proof: the boot env is a contract, and constraint edges need a real MySQL.
TL;DR: Nested categories were live with no test coverage, and even the health check was broken since it missed required env vars. I patched the env setup and added three MySQL-backed integration tests for tricky deletes that mocks would hide. They run with the integration tag and -p 1 to avoid collisions on the shared test database.
#golang#testing#mysql