Common docker compose setup
Usually when I start with development of new app, I first create docker-compose.yaml file to spin up services I need:
- 2 instances of database; one for app and one for integration tests.
- frontend - node
- backend - e.g. python
- redis, mailhog, … whatever I need
Usually I pick bullseye tag because there is no need to save on resources from the beginning if not needed. It might just slow down my development process.

Database
Notice the difference from db-test and db in tmpfs. Mysql dockerfile is designed to write data in /var/lib/mysql. If I set this folder as tmpfs it means it will live in memory which is faster and data is lost on app shutdown. Perfect for tests!
Sometimes I add a healthcheck to db so the backend app can be started only after db is ready with service_healthy predicate.
Setting logging driver to none helps reducing spamming the terminal, but only after database has fully started.
To provision database on startup you can map volume /docker-entrypoint-initdb.d/ to your folder where you hold some provision.sql like files
Backend
The important here is not to forget mapping volume for source files and also for installed packages. Otherwise you will need to install packages every time you restart your docker.
For python you need /pip-lib and /root/.cache/pip. For GOlang you can define GOPATH and GOCACHE variable where modules will be saved, but for node you don’t need anything as dependencies are by default loaded alongside your app in node_modules.
Here is full docker-compose example.
version: '3'
services:
db-test:
image: "mysql:8.0.30"
restart: always
environment:
- MYSQL_DATABASE=${DATABASE_NAME:-blog}
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD:-root}
tmpfs:
- /var/lib/mysql:rw,noexec,nosuid,size=1000m
- /tmp/:rw,noexec,nosuid,size=128m
volumes:
- ${APP_ROOT_DIR:-.}/db/conf.d/:/etc/mysql/conf.d/:cached
- ${APP_ROOT_DIR:-.}/db/initdb.d/:/docker-entrypoint-initdb.d/:ro,cached
ports:
- "3307:3306"
logging:
driver: none
db:
image: "mysql:8.0.30"
restart: always
environment:
- MYSQL_DATABASE=${DATABASE_NAME:-blog}
- MYSQL_ROOT_PASSWORD=${DB_PASSWORD:-root}
volumes:
- ${APP_ROOT_DIR:-.}/db/conf.d/:/etc/mysql/conf.d/:cached
- ${APP_ROOT_DIR:-.}/db/initdb.d/:/docker-entrypoint-initdb.d/:ro,cached
logging:
driver: none
healthcheck:
test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ]
timeout: 20s
retries: 10
tmpfs:
- /tmp/:rw,noexec,nosuid,size=128m
ports:
- "3308:3306"
mailhog:
image: mailhog/mailhog:v1.0.1
environment:
- MH_SMTP_BIND_ADDR=0.0.0.0:25
ports:
- '25800:8025'
- '25000:25'
expose:
- '25'
app:
image: python:3.10.6-bullseye
volumes:
- ${APP_ROOT_DIR:-.}/:/blog:cached
- ${APP_ROOT_DIR:-.}/lib:/pip-lib
- ${APP_ROOT_DIR:-.}/pip-cache:/root/.cache/pip
environment:
- HOST=db
- USER=root
- PASSWORD=root
- DATABASE=blog
- PORT=3306
- HOST_TEST=db-test
- DATABASE_TEST=blog
- USER_TEST=root
- PASSWORD_TEST=root
- PORT_TEST=3306
working_dir: /blog/src
entrypoint: [ "tail", "-f", "/dev/null" ]
depends_on:
- db:
condition: service_healthy
- db-test
ports:
- "8800:8800"
frontend:
image: node:lts-bullseye
volumes:
- ${APP_ROOT_DIR:-.}/frontend:/frontend
working_dir: /frontend/blog
entrypoint: [ "tail", "-f", "/dev/null" ]
depends_on:
- app
ports:
- "8880:8880"