13 KiB
title, tags
| title | tags | |
|---|---|---|
| Docker Setup |
|
Docker Setup
Walk-through of every Dockerfile, compose file, volume, and network used by the marketplace stack. Cross-references Deployment for the live-host configuration and Local Setup for developer use.
1. Backend — Dockerfile.dev
Path: /Users/mojtabaheidari/code/backend/Dockerfile.dev
FROM node:22-alpine
RUN corepack enable
WORKDIR /app
COPY package.json ./
RUN yarn install --frozen-lockfile
COPY . .
RUN mkdir -p uploads/{avatars,documents,products,temp}
RUN addgroup -g 1001 -S nodejs && adduser -S marketplace -u 1001
RUN chown -R marketplace:nodejs /app
USER marketplace
EXPOSE 5001
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
CMD ["yarn", "dev"]
Notes:
- Base.
node:22-alpine— small, glibc-musl. Corepack is enabled to use the pinned Yarn 1.22.22. - Install.
yarn install --frozen-lockfilebrings dev dependencies (needed forts-node+nodemonhot reload). - Uploads scaffold. Creates the four canonical upload directories so the API doesn't have to
mkdirat runtime. - Non-root user. Process runs as
marketplace(uid1001). Defence-in-depth. - Healthcheck.
healthcheck.jsdoes a local HTTP GET to/health(see Monitoring). - CMD.
yarn dev→nodemon --exec ts-node src/app.ts. Source code is mounted from the host so saves trigger restarts.
Used by docker-compose.dev.yml. Not pushed to the registry — dev images are local.
2. Backend — Dockerfile.prod
Path: /Users/mojtabaheidari/code/backend/Dockerfile.prod
Multi-stage build to keep the runtime image small and free of build tooling.
# ---- builder ----
FROM node:22-alpine AS builder
RUN corepack enable
WORKDIR /app
COPY package.json ./
COPY healthcheck.js ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build # tsc → ./dist
# ---- production ----
FROM node:22-alpine AS production
RUN corepack enable
WORKDIR /app
COPY package.json ./
COPY healthcheck.js ./
RUN yarn install --frozen-lockfile --production && yarn cache clean
COPY --from=builder /app/dist ./dist
RUN mkdir -p uploads/{avatars,documents,products,temp}
RUN addgroup -g 1001 -S nodejs && adduser -S marketplace -u 1001
RUN chown -R marketplace:nodejs /app
USER marketplace
EXPOSE 5001
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js
CMD ["node", "dist/app.js"]
Notes:
- Two stages.
buildercompiles TS to JS;productionkeeps only the compiled output + production deps. Final image is ~150 MB. - No dev deps.
--productionflag in the second stage trims away TypeScript, Jest, ts-node etc. - Same non-root pattern.
marketplace:nodejs(uid 1001). - CMD. Plain
node dist/app.js— no transpilation at runtime. - Uploads. The directory is created inside the image, then the running container mounts
/app/uploadsfrom a host volume in compose (overrides the embedded dir).
Built and pushed by .gitea/workflows/docker-build-no-cache.yml (and friends — see CI-CD Pipeline). The resulting image is git.manko.yoga/manawenuz/escrow-backend:<version> + :latest.
3. Frontend — Dockerfile (production)
Path: /Users/mojtabaheidari/code/frontend/Dockerfile
Multi-stage Next.js standalone build.
# ---- builder ----
FROM node:22-alpine AS builder
# (NEXT_PUBLIC_* vars set here so they bake into the bundle)
ENV NEXT_PUBLIC_API_URL=https://dev.amn.gg/api
ENV NEXT_PUBLIC_BACKEND_URL=https://dev.amn.gg
# ...more ENV lines (see file)...
RUN apk add --no-cache git python3 make g++ py3-pip
RUN corepack enable
WORKDIR /app
COPY package.json yarn.lock* ./
RUN yarn install --frozen-lockfile --production=false --network-timeout 600000
COPY src ./src
COPY public ./public
COPY next.config.ts tsconfig.json ./
COPY *.config.mjs ./
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN yarn build # produces .next/standalone + .next/static
# ---- runner ----
FROM node:22-alpine AS runner
RUN apk add --no-cache curl
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
WORKDIR /app
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
ENV PORT=8083 HOSTNAME="0.0.0.0" NODE_ENV=production
ENV NEXT_PUBLIC_SENTRY_DSN=https://...sentry.io/...
USER nextjs
EXPOSE 8083
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8083 || exit 1
CMD ["node", "server.js"]
Notes:
- Baked env vars.
NEXT_PUBLIC_*variables are set asENVin the builder stage so Next inlines them into the static bundle at build time. To deploy to a different domain you must rebuild — there is no runtime override forNEXT_PUBLIC_*. See Environment Variables#how-env-is-loaded. - System packages.
git python3 make g++ py3-pipare needed bynode-gypfor native modules (e.g.sharp,@google-cloud/local-auth). - Standalone output.
next.config.tssetsoutput: 'standalone', so the runner stage copies only.next/standalone/andpublic/— a self-contained tree with a built-inserver.js. Final runtime image: ~250 MB. - Non-root.
nextjs(uid 1001). server.jsis generated by Next.js — it embeds the necessary Node modules and starts the production server.
4. Frontend — Dockerfile.dev
Path: /Users/mojtabaheidari/code/frontend/Dockerfile.dev
FROM node:22-alpine
RUN apk add --no-cache git python3 make g++ py3-pip
RUN corepack enable
WORKDIR /app
COPY package.json yarn.lock* ./
RUN yarn config set network-timeout 600000 && \
yarn config set network-concurrency 1 && \
yarn install --frozen-lockfile --network-timeout 600000
COPY . .
EXPOSE 3000
CMD ["yarn", "dev:docker"]
Notes:
- Listens on port 3000 in dev (matches the legacy convention).
yarn dev:dockeris a variant ofdevthat binds 0.0.0.0 so the container is reachable from the host.- No multi-stage — speed > size.
Used for local development if you choose to run the frontend in Docker instead of via yarn dev. Most developers run frontend natively for HMR speed; backend in Docker for parity.
5. docker-compose.dev.yml
Path: /Users/mojtabaheidari/code/backend/docker-compose.dev.yml
name: nickapp-development
services:
nickdev-backend:
build: { context: ., dockerfile: Dockerfile.dev }
container_name: nickdev-backend
env_file: [.env.local]
ports: ["5001:5001"]
volumes:
- ./src:/app/src
- ./uploads:/app/uploads
depends_on: [mongodb, redis]
restart: unless-stopped
networks: [nickapp-network]
mongodb:
image: mongo:8.2
container_name: nickdev-mongodb
ports: ["27017:27017"]
env_file: [.env.local]
volumes: [mongodb_data:/data/db]
restart: unless-stopped
networks: [nickapp-network]
redis:
image: redis:8-alpine
container_name: nickdev-redis
env_file: [.env.local]
command: redis-server
volumes: [redis_data:/data]
restart: unless-stopped
networks: [nickapp-network]
networks:
nickapp-network: { driver: bridge }
volumes:
mongodb_data:
redis_data:
Highlights:
- No auth on Mongo/Redis in dev. Mongo runs default; Redis runs plain
redis-server. - Source mounted.
./srcis volume-mounted into the backend container so hot reload works. - Uploads mounted.
./uploadson the host is bind-mounted to/app/uploadsso files survive container restarts. - Port mappings:
5001(backend) +27017(Mongo) exposed to host. Redis is not exposed by default. - Network.
nickapp-networkbridge — Mongo/Redis are reachable asmongodb/redisfrom the backend container.
6. docker-compose.production.yml
Path: /Users/mojtabaheidari/code/backend/docker-compose.production.yml
Five services. Reproducing only the most important bits — full file lives in the repo and is summarised in Deployment#compose-file.
name: nickapp-production
services:
nginx:
image: nginx:alpine
container_name: nickapp-nginx
ports: ["8083:80"]
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/logs:/var/log/nginx
- ./uploads:/uploads
depends_on: [nickapp-backend, nickapp-frontend]
networks: [default]
nickapp-backend:
build: { context: ., dockerfile: Dockerfile.prod }
image: nickapp-backend:latest
container_name: nickapp-backend
platform: linux/amd64
env_file: [.env]
volumes: [./uploads:/app/uploads]
depends_on: [mongodb, redis]
networks: [default]
healthcheck: { test: ["CMD","curl","-f","http://localhost:5001/health"], ... }
labels: ["com.centurylinklabs.watchtower.enable=true"]
mongodb:
image: mongo:8.2
container_name: nickapp-mongodb
env_file: [.env]
volumes:
- mongodb_data:/data/db
- ./mongo-init:/docker-entrypoint-initdb.d
healthcheck: { test: ["CMD","mongosh","--eval","db.adminCommand('ping')"], ... }
redis:
image: redis:8-alpine
container_name: nickapp-redis
env_file: [.env]
command: ["sh","-lc","redis-server --requirepass \"$${REDIS_PASSWORD}\""]
volumes: [redis_data:/data]
healthcheck: { test: ["CMD","redis-cli","-a","$${REDIS_PASSWORD}","ping"], ... }
nickapp-frontend:
build: { context: ../frontend, dockerfile: Dockerfile }
image: nickapp-frontend:latest
container_name: nickapp-frontend
platform: linux/amd64
env_file: [.env]
environment: [PORT=8083, NODE_ENV=production]
expose: ["8083"]
healthcheck: { test: ["CMD","curl","-f","http://localhost:8083/"], ... }
labels: ["com.centurylinklabs.watchtower.enable=true"]
networks:
default: { driver: bridge }
volumes:
mongodb_data:
redis_data:
Key differences from dev:
- Nginx added as the public entry point.
- Backend and frontend are labelled for Watchtower auto-updates.
- Mongo and Redis are not Watchtower-managed — their major versions need manual planning + backup (Backup & Recovery).
- Redis password is read from
.env(escaped$$so docker compose doesn't expand it). - Frontend build context points at
../frontend— the two repos must live as siblings on disk. - No host port mapping for backend/frontend — they are reached only via the nginx container.
- platform: linux/amd64 is pinned because production hosts are x86_64; ARM developers must
--platform=linux/amd64if they build locally for prod.
7. Volumes
| Volume | Mount point | Lifecycle | Notes |
|---|---|---|---|
mongodb_data (named) |
/data/db in mongodb |
Persistent | The whole database. Back up via mongodump. |
redis_data (named) |
/data in redis |
Persistent | RDB snapshots + AOF if configured. |
./uploads (bind) |
/app/uploads in backend, /uploads in nginx |
Persistent on host | User-uploaded files. Critical — back up the directory. |
./nginx/nginx.conf (bind, RO) |
/etc/nginx/nginx.conf |
Static | Reverse-proxy config. |
./nginx/logs (bind) |
/var/log/nginx |
Append-only on host | Access + error logs. |
./mongo-init (bind, RO) |
/docker-entrypoint-initdb.d |
One-time | JS files Mongo runs only on a fresh datadir to create initial users / indexes. |
Inspect named volumes:
docker volume ls
docker volume inspect nickapp-production_mongodb_data
[!warning]
docker compose down -vdeletes named volumes. Never run this in production unless you've backed up first.
8. Networks
- Dev:
nickapp-networkbridge. All three services join it; the backend reachesmongodbandredisby container name. - Prod: the default compose network (also a bridge), named
nickapp-production_default. Same DNS-by-container-name semantics. Nginx talks tonickapp-backend:5001andnickapp-frontend:8083over this network.
Inspect:
docker network ls
docker network inspect nickapp-production_default
9. Image build & push from a developer machine
For a production-parity build locally (without going through CI):
cd ~/code/backend
docker build --platform=linux/amd64 -f Dockerfile.prod \
-t git.manko.yoga/manawenuz/escrow-backend:test .
# Sanity-check size + run
docker images git.manko.yoga/manawenuz/escrow-backend
docker run --rm -p 5001:5001 --env-file .env.local \
git.manko.yoga/manawenuz/escrow-backend:test
For the official path (build + push to registry) use ./scripts/build-and-push.sh — see Scripts#build-and-push-sh — or rely on CI-CD Pipeline to do it on every push.
10. Image cleanup
Builds accumulate. Periodically prune:
docker system prune -a -f
docker volume prune -f # ⚠ removes unused named volumes — check first
docker builder prune -a -f # buildx cache
# scripted (backend)
npm run docker:clean
docker:clean runs docker system prune -a -f && docker volume prune -f — confirm you don't need anything before you run it.
[!warning]
docker volume prunewill deletemongodb_dataandredis_dataif their compose project is currentlydown. Always rundocker compose up -dfirst to keep the volumes "in use".