# docker-flask-example is a Flask app skeleton with the Docker parts done

> nickjj's docker-flask-example wires a Flask 3 application to PostgreSQL, Redis and Celery with Docker Compose, esbuild and Tailwind, and it is opinionated about how a containerised Flask service should be put together. The value is in the wiring and the opinions, not in the hello world it renders.

**nickjj/docker-flask-example** — A production ready example Flask app that's using Docker and Docker Compose.

- Repository: https://github.com/nickjj/docker-flask-example
- Stars: 765 · Forks: 139
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/nickjj-docker-flask-example

## A base project and a Dockerisation guide in one

The project is described as a production ready example Flask app using Docker and Docker Compose, and the README offers two ways to read it: as a base for a new project, or as a guide to Dockerize an existing Flask app.

The framing that makes it interesting is the claim about minimality. The README says the example app is minimal but wires up a number of things you might use in a real world Flask app, and at the same time it is not loaded up with a million personal opinions. The Docker side is described as an accumulation of Docker best practices, based on building and deploying dozens of assorted Dockerized web apps since late 2014.

That is a fair description of what the repository is. The application itself is a hello world, and the value is in the arrangement around it: the database, the cache, the worker, the asset build, the environment handling and the deployment configuration.

The stack is current rather than dated. The README says the app is using Flask 3.1.3 and Python 3.14.7, and the manifest requires Python 3.13 or newer, so the pinned versions in pyproject.toml are the versions you get, and they are recent ones.

## The stack: Postgres, Redis, Celery and an esbuild asset pipeline

The back end is PostgreSQL with SQLAlchemy, Redis, and Celery. The front end is esbuild, TailwindCSS and Heroicons.

The JavaScript question is answered honestly rather than dogmatically. Picking a JS library is a very app specific decision, because it depends on which library you like and on whether the app is going to be mostly Jinja templates with sprinkles of JS or an API back end. The README lists several reasonable choices, including Hotwire, htmx, Alpine, Vue, React and jQuery, and notes that with esbuild set up you can use any of them, or none, easily.

The author's own preference is stated too: Hotwire Turbo plus Stimulus in most newer projects.

The Python side is pinned exactly in pyproject.toml, with a name of hello and a version of 0.1.0. The dependency list is short and specific: alembic 1.20.0 for migrations, celery 5.6.3, flask 3.1.3, flask-db 0.4.1, flask-debugtoolbar 0.16.0, flask-secrets 0.1.0, flask-sqlalchemy 3.1.1, flask-static-digest 0.4.1, gunicorn 26.2.0, jinja2 3.1.6, psycopg 3.3.5, pytest 9.1.1 with pytest-cov 7.1.0, redis 8.1.0, ruff 0.16.7, setuptools 84.0.0, sqlalchemy 2.0.53, sqlalchemy-utils 0.42.1 and werkzeug 3.1.8.

The tooling is modern in one specific way. uv is used for package management instead of pip3, and the README gives the reason in a parenthesis, that builds on the author's machine are about ten times faster.

## The opinions that make it worth reading

The README has a section on notable opinions and extensions, and the reasoning behind it is the first line of it: Flask is a very unopinionated framework, but the same things get added over and over in most apps.

The listed additions are all from the same author. gunicorn is the app server in both development and production. Flask-DB helps manage, migrate and seed the database. Flask-Static-Digest tags and gzips the static files and adds optional CDN support. Flask-Secrets generates secure random tokens, and Flask-DebugToolbar shows useful information while debugging.

For linting and testing, ruff lints and formats the code base and pytest with pytest-cov writes the tests and reports coverage. Ruff is configured with a line length of 79 and the lint rules extended with I, SIM and E501.

The application itself adds two blueprints, a page blueprint that renders a / page and an up blueprint that provides health check pages. The config changes are about container friendliness: logging goes to STDOUT so Docker can consume the output, a bunch of settings are pulled into environment variables, and config/settings.py plus the .env file handle configuration in all environments.

There are also deliberate changes to Flask defaults. The public directory is the static directory, static_url_path is set to an empty string to remove the /static URL prefix, and the ProxyFix middleware is enabled.

## Compose profiles instead of one giant service list

The compose file is where the design shows, and the mechanism is profiles.

Postgres and Redis are each behind a profile of their own, tagged postgres and redis, with the images pinned as postgres:18.6-trixie and redis:8.10.1-trixie. Both declare deploy resource limits for cpus and memory that are configurable through environment variables and default to 0, which means no limit unless you set one. Each also has a restart policy from a variable, a three second stop grace period and a named volume for its data.

The app service inherits from an anchor named default-app, and the assets service from default-assets, both YAML anchors so the build arguments are declared once. The build arguments are the interesting bit, because they are what makes the container run as you rather than as root:

```yaml
  build:
    context: "."
    target: "app"
    args:
      - "APP_UID=${UID:-1000}"
      - "APP_GID=${GID:-1000}"
      - "FLASK_DEBUG=${FLASK_DEBUG:-false}"
      - "NODE_ENV=${NODE_ENV:-production}"
```

The depends_on entries for postgres and redis are marked required: false, which is the Compose v2 feature that lets the app start without them, and the .env file comments explain why you would want that, since you can use managed cloud services instead.

The assets service is the development asset watcher and it lives behind a profile of its own, so it does not run in production where assets are built into the image.

## Running it takes three commands and a first build of five to ten minutes

The running instructions are the shortest part of the README, which is a sign the project is aimed at people who have done this before.

Clone the repository and move into the directory:

```sh
git clone https://github.com/nickjj/docker-flask-example helloflask
cd helloflask
```

Then copy the example environment file, because the real one is git ignored:

```sh
cp .env.example .env
```

And then build everything:

```sh
docker compose up --build
```

The README warns that the first run takes five to ten minutes depending on your internet connection speed and your hardware, because it downloads a few Docker images and builds the Python and Yarn dependencies. After that everything is running and the app can be treated like any other Flask app.

There is a port collision note as well. If you get an error about a port being in use, the likely cause is something already running on port 8000, and the .env file documents how to change it.

Two environment requirements are worth stating. Docker Compose v2 support must be enabled if you use Docker Desktop, and on native Linux you install Compose as a plugin to Docker. The project uses specific Docker Compose v2 features that only work with Compose v2 2.20.2 or newer. On Windows, the expectation is that you are working inside WSL or WSL 2, since the commands are shell commands, with a note that you can modify them for PowerShell.

## The environment file is where the decisions live

The .env.example is the most instructive file in the repository, and its comments explain the philosophy.

The defaults are optimised for production so that little has to be configured in production, but the comments say that an uncommented option means it is either mandatory to set or it is being overwritten in development to make your life easier. That is a useful convention, because it tells you at a glance which lines are load bearing.

The profiles line is the switchboard:

```
#export COMPOSE_PROFILES=postgres,redis,web,worker
export COMPOSE_PROFILES=postgres,redis,assets,web,worker
```

The commented variant drops the assets profile, which is the production shape, since in development you want the asset watchers running but in production the assets are built into the image. There is also an APP_UID and APP_GID pair that default to the host user's identifiers, and a note that you should generate a random string of 99 or more characters for SECRET_KEY in production, with the command to do it, ./run flask secrets.

The example value that ships in the file is SECRET_KEY=insecure_key_for_dev, which is exactly what it says on the tin.

PYTHONDONTWRITEBYTECODE is set to true to avoid writing bytecode into __pycache__ directories in development, and the file also exports DOCKER_BUILDKIT=1, because BuildKit is what makes the image builds with the cache behaviour the project relies on.

## The Dockerfile builds assets and the app separately

The Dockerfile is a multi stage build with a clear division. The assets stage is node:24.21.0-trixie-slim, and the app build stage is python:3.14.7-slim-trixie.

In the assets stage, the node user and group are remapped to the APP_UID and APP_GID build arguments, node_modules and the app directory are chowned, the user switches to node before the copy, and the asset build is conditional on NODE_ENV, running the JS and CSS builds only when the environment is not development.

The app stage installs build-essential, curl and libpq-dev, creates a python user with the given uid and gid, and copies the uv and uvx binaries in from the ghcr.io/astral-sh/uv image at version 0.8.17. It then copies pyproject.toml and uv.lock and the bin directory before the rest of the source, so that the dependency layer is cached independently of the application code, which is the standard trick for fast image rebuilds.

The environment sets PYTHONUNBUFFERED, PYTHONPATH, UV_COMPILE_BYTECODE, UV_PROJECT_ENVIRONMENT pointing at the user local directory, and adds that directory to the PATH.

The repository root carries a .hadolint.yaml, so the Dockerfile itself is linted, along with a .dockerignore, a compose.yaml, a run script, an alembic.ini, a pyproject.toml and a uv.lock.

The last push was on 2026-09-15 and the repository is not archived, and the licence is MIT.

## Conclusion

docker-flask-example suits a developer starting a Flask service who wants the container, database, cache, worker and asset pipeline already arranged, since the Compose profiles, the health checks, the non-root user mapping and the environment file are all decisions you would otherwise make from scratch. It is the wrong starting point if your project is not a Flask app, or if you want a minimal dependency list, because the opinionated set is the point. Before adopting it, read the .env.example comments, decide which Compose profiles you actually need in production, and set a real SECRET_KEY rather than the development placeholder.

## FAQ

### How do I run the docker-flask-example app?

Clone the repository, copy .env.example to .env, then run docker compose up --build. The first run takes five to ten minutes because it downloads Docker images and builds the Python and Yarn dependencies, and you need Docker Compose v2 2.20.2 or newer for the features the compose file uses.

### What do the Docker Compose profiles in docker-flask-example do?

Postgres and Redis each sit behind a profile of their own, and the assets service is behind the assets profile so it does not run in production where assets are built into the image. The .env.example sets COMPOSE_PROFILES to postgres,redis,assets,web,worker in development and shows the production variant without assets.

### Which Python and Flask versions does docker-flask-example use?

The README says the app is using Flask 3.1.3 and Python 3.14.7, and pyproject.toml requires Python 3.13 or newer with flask pinned at 3.1.3, gunicorn 26.2.0, sqlalchemy 2.0.53, celery 5.6.3 and ruff 0.16.7.

## Sources

- [Issues](https://github.com/nickjj/docker-flask-example/issues)
- [License: MIT](https://github.com/nickjj/docker-flask-example/blob/main/LICENSE)
- [nickjj/docker-flask-example on GitHub](https://github.com/nickjj/docker-flask-example)
- [README](https://github.com/nickjj/docker-flask-example/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nickjj-docker-flask-example
