Docker Compose Setup for a Dev Environment
An example of how to set up a development environment in Docker using docker compose. These are NOT builds for production, and therefore use a Docker build stage identified as DEV
. (You can call this whatever you want really). Using Docker build stages allows you to cleanly add on to the Dockerfile later for a production build.
The example is for a React Vite app with an Express backend.
Vite App
package.json
Ensure you add --host
to your dev script. This will allow you to access the app from outside of the container.
1 "scripts": {
2 "dev": "vite --host", // <-- Ensure you add --host
3 "build": "vite build",
4 "lint": "eslint .",
5 "preview": "vite preview"
6 },
Dockerfile
The dockerfile is straight forward. We don’t need to copy any files because we will pass everything in with a bind mount.
1FROM node:24-alpine as DEV
2WORKDIR /src/app
3CMD ["npm", "run", "dev"]
Express App (API)
package.json
For the API, we’ll need to create a dev
script that will run the app via nodemon. We also need a build-db
script that will run database migrations and seeds.
1 "scripts": {
2 "start": "node server.js",
3 "dev": "nodemon server.js",
4 "build-db": "npx knex migrate:latest && npx knex seed:run"
5 },
Dockerfile
Then in the Dockerfile, we run both of those scripts with the &&
shell operator. Using JSON notation like the Vite app’s Dockerfile is recommended and your editor may yell at you for not using it (as I don’t in this example). But the shell operator won’t work in JSON notation, so I do it this way.
1FROM node:24-alpine as DEV
2WORKDIR /src/app
3CMD npm run build-db && npm run dev
.env file
Ensure you have a .env
file for any environment variables that your code may be using. Here’s my example:
VITE_API_PROTO=http
VITE_API_HOST=localhost
VITE_API_PORT=3001
NODE_ENV=development
CORS_ORIGINS=http://localhost:5173
JWT_SECRET=supersecretjwtsaucethatnobodycanguess
COOKIE_SECRET=supersecretcookiesaucethatnobodycanguess
DB_NAME=clearminder
DB_HOST=db
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=docker
Docker Compose
Finally, here’s the docker compose that runs everything, referencing environment variables from the .env
file. Notice that API and UI (vite) containers have volumes (bind mounts) to the code in the repo. This is why we don’t need to run a COPY
or npm install
operation within the Dockerfile.
1---
2services:
3 db:
4 image: postgres
5 hostname: clearminder-db
6 container_name: clearminder-db
7 restart: unless-stopped
8 # set shared memory limit when using docker compose
9 shm_size: 128mb
10 environment:
11 POSTGRES_USER: ${DB_USER}
12 POSTGRES_PASSWORD: ${DB_PASSWORD}
13 POSTGRES_DB: ${DB_NAME}
14 ports:
15 - "5432:5432"
16 volumes:
17 - "./dev-pg-data:/var/lib/postgresql"
18 api:
19 build:
20 context: api/
21 target: DEV
22 hostname: clearminder-api
23 container_name: clearminder-api
24 restart: unless-stopped
25 ports:
26 - "3001:3001"
27 volumes:
28 - "./api:/src/app"
29 environment:
30 - CORS_ORIGINS=${CORS_ORIGINS}
31 - JWT_SECRET=${JWT_SECRET}
32 - COOKIE_SECRET=${COOKIE_SECRET}
33 - DB_USER=${DB_USER}
34 - DB_PASSWORD=${DB_PASSWORD}
35 - DB_NAME=${DB_NAME}
36 - DB_HOST=${DB_HOST}
37 - DB_PORT=${DB_PORT}
38 depends_on:
39 - db
40 ui:
41 build:
42 context: ui/
43 target: DEV
44 hostname: clearminder
45 container_name: clearminder
46 restart: unless-stopped
47 ports:
48 - "5173:5173"
49 environment:
50 - VITE_API_PROTO=${VITE_API_PROTO}
51 - VITE_API_HOST=${VITE_API_HOST}
52 - VITE_API_PORT=${VITE_API_PORT}
53 volumes:
54 - "./ui:/src/app"
55 depends_on:
56 - db
57 - api