Code Review · 9 May 2026

vue-django-template

Verdict

Solid bones, but prod is broken in a few load-bearing places. 6/10 as-is, 9/10 with about 2 hours of polish. Architecture choices are right; execution has not caught up to the design.

Showstoppers (prod will not work)

1

The nginx service has no config and no static frontend assets.

docker-compose.yml:44-53 runs nginx:alpine with no nginx.conf mount. Default nginx serves the empty /usr/share/nginx/html. So in prod: no frontend, no /api proxy, no /static/, no /media/. The volume mounts at /app/staticfiles are not even on nginx's serving path.

Meanwhile frontend/Dockerfile:8-10 builds a different nginx image with the Vue dist embedded at /usr/share/nginx/html, but the frontend service never publishes a port and is not proxied to. So that nginx is also unreachable.

Fix: drop one of them. Easiest path: delete the standalone nginx service, give the frontend service the 80:80 port mapping, and have it proxy_pass /api/ to backend:8000 and serve /static/ + /media/ from a shared volume. Add an actual nginx.conf.
2

collectstatic is shadowed by the named volume.

backend/Dockerfile:10 runs collectstatic at build time and writes to /app/staticfiles in the image. docker-compose.yml:33 then mounts the static_files named volume at the same path. On first run Docker copies image contents into the empty volume (works), but on every subsequent rebuild the volume is stale. New CSS or admin assets will not appear until you docker volume rm static_files.

Fix: run collectstatic --noinput in entrypoint.sh after mount, not at image build time.
3

DRF urls clash with API namespace.

backend/app/urls.py:6 mounts rest_framework.urls (the browsable login/logout views) at /api/. The Vite proxy (vite.config.js:10) routes /api/* to backend. Fine for now, but the moment you add a real API route at /api/users/ it will work, until DRF's /api/login/ tries to take it. Convention is /api-auth/.

Real risks

4

psycopg2-binary in prod.

Psycopg maintainers explicitly say not to ship -binary to production (libpq linkage issues). Use psycopg[binary]==3.x (psycopg3, Django 4.2+ supported) or build psycopg2 from source. requirements.txt:4.

5

Hard-coded gunicorn workers.

backend/Dockerfile:15 uses --workers 4 regardless of CPU. Should be --workers ${GUNICORN_WORKERS:-3} or computed from cores at entrypoint.

6

No backend healthcheck.

db has one (good), backend does not. Nginx will happily route to a 500-ing app.

7

No prod security headers.

base.py is missing the standard prod toggles:

SECURE_SSL_REDIRECT = config('SECURE_SSL_REDIRECT', default=False, cast=bool)
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_HSTS_SECONDS = config('SECURE_HSTS_SECONDS', default=0, cast=int)
SESSION_COOKIE_SECURE = not DEBUG
CSRF_COOKIE_SECURE = not DEBUG

A template that ships without these encourages people to skip them forever.

8

Migrations on every container start.

entrypoint.sh:3. Fine for single-instance, race condition the moment you scale to more than one replica. Worth a comment in the README pointing to a separate migrate job for k8s or Swarm later.

9

SECRET_KEY=change-me-in-production in .env.example.

Better to ship empty (SECRET_KEY=) so config('SECRET_KEY') raises at boot if forgotten. A copy-pasted string is more dangerous than a hard fail.

DX nits

10

backend/app/ naming.

Project module called app while WORKDIR=/app makes from app.settings.base resolve via /app/app/settings/base.py. Works, but reads like a bug. Most Django templates use backend/config/ or backend/<projectname>/.

11

No frontend env scaffolding.

No frontend/.env.example, no VITE_API_BASE_URL pattern. First thing every consumer of this template will add.

12

No CORS config in prod.

dev.py has CORS_ALLOW_ALL_ORIGINS = True. base.py has the middleware loaded but no CORS_ALLOWED_ORIGINS. In prod everything is same-origin via nginx so it does not matter. So why is the middleware there at all? Either drop it from prod or set CORS_ALLOWED_ORIGINS from env.

13

No tests, no CI, no linter, no pre-commit.

Acceptable for a template, but a 5-line .github/workflows/ci.yml running ruff + npm run build would catch 80% of regressions when consumers fork it.

14

INSTALLED_APPS has no app of its own.

README says python manage.py startapp but there is no example app, no example serializer, no example viewset. New users will hit "what goes where?" on day 1. One toy app (api/ with a HealthView) would make the template self-demonstrating.

15

entrypoint.sh only on prod Dockerfile.

Dev Dockerfile does not copy it, dev compose runs runserver directly. So dev never auto-migrates. Inconsistent.

What I would actually change first

If you are going to use this for a real project tomorrow, fix these in order. Roughly 2 hours of work.

  1. Write nginx/nginx.conf, mount it, kill the duplicate frontend nginx (showstopper).
  2. Move collectstatic to entrypoint.sh (silent staleness bug).
  3. Move DRF auth to /api-auth/ (5-second fix, saves a future rename).
  4. Add frontend/.env.example + VITE_API_BASE_URL (every consumer needs it).
  5. Add a backend healthcheck and remove the hard-coded --workers 4.