Build & Deploy¶
Build / scripts¶
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon server.js",
"dev": "nodemon server.js"
}
}
Both start and dev invoke nodemon. As elsewhere in the platform, production presumably overrides this via PM2 node server.js (verify with the ops owner).
Dockerfile (multi-stage, optimised)¶
A well-crafted multi-stage Dockerfile is committed. Highlights:
# Stage 1: Builder
FROM node:20.19.2-slim AS builder
ENV NODE_ENV=production \
YARN_CACHE_FOLDER=/tmp/.yarn-cache \
YARN_NETWORK_CONCURRENCY=8
WORKDIR /usr/src/app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production=true --prefer-offline --link-duplicates && \
find node_modules -type d \( -name "test*" -o -name "example*" -o -name "docs" -o -name ".git" \) -prune -exec rm -rf {} + && \
find node_modules -type f \( -name "*.md" -o -name "*.ts" -not -path "*/typescript/*" -o -name "LICENSE*" -o -name "CHANGELOG*" -o -name "*.o" \) -delete && \
yarn cache clean
# Stage 2: Runtime
FROM node:20.19.2-slim
WORKDIR /usr/src/app
RUN apt-get update && apt-get install -y --no-install-recommends \
nginx libnss3 libexpat1 fontconfig && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY .env conf.js favicon.ico ./
COPY nginx.conf /etc/nginx/nginx.conf
COPY *.js ./
COPY routes/ ./routes/
COPY modules/ ./modules/
COPY helper/ ./helper/
COPY actions/ ./actions/
COPY conf/ ./conf/
RUN echo '#!/bin/bash\nnginx -g "daemon off;" &\nnode server.js' > /entrypoint.sh && \
chmod +x /entrypoint.sh
EXPOSE 80 8080
CMD ["/entrypoint.sh"]
Notable:
- Two-stage: builder strips test files / docs / LICENSE / CHANGELOG /
.ofiles fromnode_modulesto slim the runtime image - Runtime libs for Puppeteer:
libnss3,libexpat1,fontconfigare present — needed for headless Chrome - nginx in same container: not a sidecar; nginx is launched alongside node via the entrypoint script. nginx + node share the container's network and file system.
.envis COPY'd into the image: this means the.envfile must exist in the build context and is baked into the image. Anyone who pulls the image can read the secrets. This is a finding — see security.md. Better: pass.envas a runtime mount (docker run -v), or use Docker secrets / env vars on the orchestrator.
nginx.conf¶
In the repo. Not inspected in detail in this audit; should be audited for:
- Reverse-proxy target (likely
127.0.0.1:5002or similar) - HSTS / X-Frame-Options / Referrer-Policy / X-Content-Type-Options headers
- Body-size limit
- Suppressed server header
push.sh — manual Docker → ECR image push¶
push.sh is not an SSH/git-pull script — it builds and pushes a Docker image to ECR via deploy_image.py:
#!/bin/bash
python deploy_image.py \
--image-tag "dapi" \
--repository-uri 255061853867.dkr.ecr.us-west-2.amazonaws.com/designers \
--dockerfile /home/sanjai/designer-api
- ECR repos (two on the designer side):
designers— the designer-api service image thatpush.shtargets — anddesigners-cronsfor the designer cron/worker fleet (57 jobs + 6 bots). Both are in account255061853867, regionus-west-2, separate from the someli-api crons repos (dev_crons/uat/live_crons). See ../DEPLOYMENT-OVERVIEW.md §1. Verify with ops how thedesigners-cronsimage is built/pushed —push.shonly coversdesigners. - Tag:
dapi, overwritten on every push — no image versioning (see D-7). - Hardcoded workstation path
/home/sanjai/designer-api— releases break for anyone butsanjai(truck factor of 1; same antipattern assomeli-api'spush.sh). deploy_image.pyauthenticates to ECR via boto3, runsdocker build, tags, and pushes. The ECS task definition / service that consumes thedesignersimage lives in AWS, not in this repo — verify with ops which cluster/service runs it.
No CI¶
- No
Jenkinsfile - No
.github/workflows/— unlikesomeli-platform, there is no automated ECS pipeline (uat_app_ecs.yml) here; the image push is fully manual viapush.sh. someli-apihas both Jenkins and GitHub Actions (for the Lightsail dev path);designer-apihas neither in repo.
This is a gap — see D-2.
PM2 management¶
No ecosystem.config.js. With 57 jobs + 6 bots + the main server, the deploy box must somehow pm2 start each one. Either:
- The team does this manually on first deploy and PM2 saves the list via
pm2 save/pm2 startup - There's an external ops repo with PM2 manifests
- Everything runs inside
server.js's process via therequire()of the bots (which also kicks off their crons) — would mean PM2 only manages one process
Audit recommendation: commit an ecosystem.config.js. If everything is in one process, document it; if there are many processes, declare them.
Health checks¶
No /health endpoint. nginx may have its own health-check that just verifies the upstream responds. Verify.
Rollback¶
- Docker:
docker run <previous-tag>(if tags are kept) - Bare-metal:
git checkout <previous-sha> && pm2 restart
Neither is documented in repo.
Recommended next steps¶
| ID | Recommendation | Effort |
|---|---|---|
| D-1 | Remove .env COPY from Dockerfile; use runtime mount or orchestrator env vars |
Small |
| D-2 | Commit an automated ECS deploy workflow modelled after someli-platform/.github/workflows/uat_app_ecs.yml (OIDC → ECR login → build/push SHA-tagged image to designers → render+deploy task definition) instead of the manual push.sh |
Small |
| D-3 | Commit ecosystem.config.js with all processes declared |
Small |
| D-4 | Add a /health endpoint |
Trivial |
| D-5 | Audit nginx.conf for security headers |
Small |
| D-6 | Document the rollback runbook | Trivial |
| D-7 | Tag Docker images with the git SHA so rollback is well-defined | Small |