Build & Deploy¶
Status: Audit v0.1 (2026-05-09).
Existing project documentation
../07-build-and-deploy.mdcovers the Dockerfile stages, the EFS mount semantics, and the Jenkins pipeline (including the warning-grep / exit-code-100 mechanism) in narrative depth. This audit doc focuses on operational maturity and findings; the dev doc focuses on developer mental model. Read both.
1. Build tool¶
| Property | Value | Source |
|---|---|---|
| Framework | Nuxt 2.18.1 | package.json:67 |
| Bundler | Webpack 4 (bundled with Nuxt 2) | package.json:113 |
| Babel | @babel/plugin-proposal-private-methods configured |
nuxt.config.js:325-330 |
transpile includes |
@fullcalendar.*, swr |
nuxt.config.js:331 |
| Polotno bundler | Parcel 2 | polotno-editor/package.json |
2. Build command¶
yarn build # → nuxt build (writes .nuxt/)
yarn generate # → nuxt generate (writes dist/ for static hosting)
yarn start # → nuxt start (runs the SPA server from .nuxt/dist/)
The runtime in production is yarn start (Dockerfile final stage runs nuxt start). yarn generate produces the static-site snapshot in dist/, but per the start.sh deploy script that runs the nuxt start server, the live app is dynamic, not static.
The dist/ directory committed in the repo (78 MB) is a snapshot from a nuxt generate run; it is not the active production artifact.
3. Build time¶
[VERIFY-BD-1] measure on a developer machine. Expect 2–5 minutes for a clean build of both projects, given the size.
4. Build artifact¶
| Path | Contents |
|---|---|
.nuxt/dist/client/*.js |
Per-route JS chunks for the SPA |
.nuxt/dist/server/ |
Server-side runtime (small for ssr: false) |
dist/_nuxt/*.js |
(only present after nuxt generate) — same chunks as .nuxt/dist/client but at the static-site root |
polotno-bundle.js, polotno-bundle.css |
Output of polotno-editor Parcel build, written to repo root |
Sizes are inventoried in performance.md.
5. Source maps¶
Production builds do not ship source maps. Confirmed via ls dist/_nuxt/*.js.map. This is good for security (no code disclosure) but bad for observability — production stack traces are minified and unusable without uploading source maps to an error tracker (which doesn't exist — see observability.md).
nuxt.config.js:312-318 sets dev-only source-map config:
build: {
extend(config, ctx) {
if (ctx.isDev) {
config.devtool = ctx.isClient ? 'source-map' : 'inline-source-map'
}
}
}
In production (!ctx.isDev), devtool is unset; webpack 4's default for production is eval (for in-browser) or no source map for the build output. Confirmed by the empty grep.
Finding [WC-BD-1] (Severity: Medium): Production source maps are not uploaded to an error tracker (no error tracker exists). When errors do occur in production, stack traces are unreadable. Phase 0 — set up Sentry, upload source maps to Sentry, don't ship them to clients.
6. Deployment target¶
The production deploy is AWS Lightsail for dev_app per .github/workflows/dev_app.yml:1. UAT runs on AWS ECS per .github/workflows/uat_app_ecs.yml.
Two parallel CI/CD pipelines exist:
- GitHub Actions → AWS Lightsail (the live
dev_app.ymlworkflow). Deploysdev_appbranch. - Jenkins → SSH → EC2 (the existing
Jenkinsfile). Deploysdev_appto a configured server (54.190.153.94).
These are likely alternative deploy paths for the same environment, with one being current and the other legacy. [VERIFY-BD-2]: which is the actual current deploy mechanism? If both run, they may conflict.
The Jenkinsfile was inspected for 01-local-setup.md. The GH Actions workflows are newer (existing project doc 07-build-and-deploy.md does not yet describe them — it should be updated).
7. The Dockerfile¶
Dockerfile (referenced in 07-build-and-deploy.md) is a 3-stage build:
- Build polotno-editor (Parcel) →
polotno-bundle.*to/tmp - Build Nuxt main app (webpack) with the polotno bundle copied in →
.nuxt/ - Runtime:
node:20.18.3-slim+ nginx, withnode_modulesmounted from EFS at runtime viastart.sh
Findings already documented:
- start.sh waits up to 60s for EFS node_modules to mount; a missing mount = boot failure.
- The nuxt symlink in node_modules/.bin/ is recreated at boot if missing (EFS sometimes loses symlinks).
- nginx fronts Nuxt on port 80.
- client_max_body_size 200M for media uploads.
- No security headers in nginx.conf (cross-ref security.md § WC-SEC-7).
8. CI/CD pipeline¶
8.1 GitHub Actions — dev_app.yml¶
Triggers on push to dev_app. The first ~150 lines do not run a build; they extract commit info and PR details for Teams / Slack notification (the actual build is later in the file — [VERIFY-BD-3] read the full file and capture the build step).
Hypothesis: the workflow likely:
- Checks out code
- Builds the Docker image
- Pushes to ECR (or Lightsail's container registry)
- Triggers a Lightsail deployment
Confirm with full .github/workflows/dev_app.yml review.
8.2 GitHub Actions — uat_app_ecs.yml¶
Targets ECS for UAT. Mechanically similar: build, push, deploy. [VERIFY-BD-4] read full file.
8.3 Jenkins (legacy)¶
Jenkinsfile deploys via SSH to 54.190.153.94. PM2 process dev_app. Probably superseded by GitHub Actions but left in place.
The Jenkins pipeline's warning-grep / exit-code-100 mechanism is documented in detail in ../07-build-and-deploy.md § "Build warnings → exit code 100". The 16 critical-warning patterns the pipeline scans for include:
Two component files resolving to the same namecomponent files resolving to the same name,Duplicate fileModule not found,Cannot resolve,Failed to resolveWARN,WARNING,deprecatedcompilation error,Failed to compile,Type error,TypeErrorSyntaxError,Parse error,Unexpected tokenIcon not found,Image not found,File not found,Cannot find,ENOENTmonths old,update-browserslist-db
A match → exit code 100 → "succeeded with warnings" Teams notification, but the deploy still happens. Treat exit-100 deploys as PR cleanup, not "good enough" (per ../20-contributing.md § "Common failure modes").
Finding [WC-BD-2] (Severity: Medium): Two parallel CI/CD systems (GitHub Actions + Jenkins). At best, one is dead code; at worst, both run on dev_app push and conflict. Audit and decommission one.
9. Preview deployments¶
There is no vercel.json, netlify.toml, or equivalent. No preview-per-PR deploys observed. .github/workflows/dev_app.yml triggers only on push: branches: [dev_app].
Finding [WC-BD-3] (Severity: Medium): No preview deployments. Reviewers cannot see PR-as-deployed before merging. Combined with no automated tests (testing.md), this means review is "trust the diff." Phase 0/1 — add per-PR deploy via GitHub Actions to a Lightsail preview slot or to a temporary subdomain.
10. Rollback¶
The deploy script in Jenkinsfile does pm2 reload or starts fresh. There is no documented rollback recipe in this repo.
[VERIFY-BD-5]: how does the team revert a bad dev_app deploy? Manual git revert + push, or is there a runtime rollback mechanism?
For Lightsail / ECS, the natural rollback is to redeploy the previous Docker image tag.
Finding [WC-BD-4] (Severity: Medium): Rollback mechanism is undocumented. Phase 0 — write a one-page runbook for "the dev_app deploy is broken, restore previous version."
11. Environment configuration¶
| Environment | Branch | Deploy target | Backend (API_URL) |
|---|---|---|---|
| Dev | dev_app |
AWS Lightsail | (dev backend — verify) |
| UAT | uat_app |
AWS ECS | (UAT backend — verify) |
| Prod | main ? |
(verify) | (prod backend — verify) |
[VERIFY-BD-6]: confirm the branch → environment mapping. The .github/workflows/uat_app_ecs.yml is the strongest evidence for UAT-from-uat_app; prod has no visible workflow.
Env vars are injected via Docker --build-arg (per Dockerfile); the GitHub Actions workflows must be passing these as build-args.
12. CDN / static asset serving¶
dist/_nuxt/*.jschunks are content-hashed (e.g.,db8106d.js).nginx.confdoes not setCache-Controlheaders — relies on browser ETag.- Service worker (
@nuxtjs/pwa) caches/_nuxt/*cache-first. - No CloudFront or other CDN layer is documented in this repo. [VERIFY-BD-7]: is CloudFront in front of Lightsail? Most production AWS setups put one there.
(Cross-ref performance.md § Caching for the cache-control improvements.)
13. Dockerfile configuration risks¶
| Concern | Status |
|---|---|
| Image base | node:20.18.3-slim — Node 20 LTS (good); minor version pinned (good) |
| Image size | ~80 MB main Node + nginx + EFS-mounted node_modules (large) |
.dockerignore |
Excludes node_modules, dist, .nuxt, .git, .github, etc. — good |
| Non-root user | Not set — container runs as root |
| HEALTHCHECK | curl -f http://localhost:80/ every 30s |
| Secrets via build-args | Yes (env vars baked into bundle at build time, then .env deleted in stage 2) |
Finding [WC-BD-5] (Severity: Medium): Container likely runs as root. [VERIFY-BD-8] add a USER node directive in stage 3.
Finding [WC-BD-6] (Severity: Medium): HEALTHCHECK only verifies nginx; doesn't verify the Nuxt process is alive (a wedged Nuxt process behind a healthy nginx fails 502s but passes the healthcheck). Add a deeper check, e.g., curl -f http://localhost:80/heartbeat (assuming /heartbeat returns something specific).
14. EFS-mounted node_modules¶
start.sh waits up to 60s for /usr/src/app/node_modules and /usr/src/app/polotno-editor/node_modules to be EFS-mounted. The architecture is deliberate (saves image space) but fragile:
- An EFS outage = container can't boot.
- Symlinks may not survive EFS round-trips (script recreates
node_modules/.bin/nuxtif missing). - Layer caching is not used for node_modules.
Finding [WC-BD-7] (Severity: Medium): EFS-mounted node_modules is an unconventional choice. The savings (image size) are modest; the fragility is real. Recommendation: during the migration to Vue 3 / Nuxt 3, reconsider — bake node_modules into the image as a normal Docker layer.
15. Recommendations summary¶
| Priority | Action | Effort |
|---|---|---|
| Phase 0a | Verify which CI/CD is canonical (GH Actions vs Jenkins); decommission the other | <1d |
| Phase 0a | Read full .github/workflows/dev_app.yml and uat_app_ecs.yml; document the actual pipeline in this doc |
1d |
| Phase 0a | Document the rollback procedure | <1d |
| Phase 0 | Add preview-per-PR deploys | 1 week |
| Phase 0 | Set up Sentry; upload source maps from CI | 1 week |
| Phase 0 | Add Cache-Control: immutable for /_nuxt/ assets in nginx |
<1d |
| Phase 0 | Add security headers in nginx (security.md § WC-SEC-7) |
1d |
| Phase 1 | Bake node_modules into Docker image; remove EFS dependency |
1 week |
| Phase 1 | Run container as non-root; tighten HEALTHCHECK | 1d |
16. Open questions¶
Tracked in ./verify-markers.md:
- [VERIFY-BD-1] Build time on a developer machine
- [VERIFY-BD-2] Canonical CI/CD: GH Actions vs Jenkins
- [VERIFY-BD-3] Full
dev_app.ymlbuild steps - [VERIFY-BD-4] Full
uat_app_ecs.ymlbuild steps - [VERIFY-BD-5] Rollback mechanism
- [VERIFY-BD-6] Branch → environment → backend mapping
- [VERIFY-BD-7] CloudFront / CDN in front of Lightsail
- [VERIFY-BD-8] Container user (root or non-root)