python flask docker

How to run your Python Flask server inside a readonly Docker container

Published March 7, 2023 in technology
Reading time: 3 minutes

In a previous blog we showed you how to strangle old code using Python decorators. This 5 minute blog post shows you how to run a Python Flask server in a readonly Docker container. The steps are implemented in the public tdd repo.

docker-compose.yml

The yaml to run a Flask server inside a read-only container is simple:

services:
  xy_demo:
    ...
	read_only: true
	tmpfs: [ /tmp ]

Python cache files

When Python runs it creates .pyc cache files close to the cached files. It can’t do this in a read-only container. You can set the PYTHONPYCACHEPREFIX environment variable in the server’s Dockerfile to a directory it can write to, such as a subdir of /tmp.

ENV PYTHONPYCACHEPREFIX=/tmp/py_caches

Typical asset bundling won’t work

In a typical server startup, asset files are bundled together. In the example below, all the separate CSS files in the static/css/ directory get bundled into a new CSS file called bundle.css in the same directory:

from flash import Flask
from flask_assets import Environment, Bundle
from pathlib import Path

def app():
	server = Flask(__name__)
	assets = Environment(server)
	init_css(server, assets)
	...

def init_css(server, assets):
	css_files = asset_file_paths(server, "css")
	css = Bundle(*css_files, filters='cssmin', output='bundle.css')
	server.register('css', css)

def asset_file_paths(server, dir_name):
	static_path = Path(f'{server.root_path}/static/{dir_name}')
	return [f'{static_path}/{file.name}' for file in static_path.iterdir()]	

This won’t work in a read-only container because Python will not be able to create new files inside the ../static/css/directory.

Static asset pre-bundling

To overcome this problem, Kosli pre-bundles the SCSS and JS files in a separate step which runs before building the server image. The bash commands to do this are:

GIT_COMMIT_SHA=$(git rev-parse HEAD)docker run --rm \
	--volume "${HOST_ROOT_DIR}/package.json:/app/package.json:ro" \
	--volume "${HOST_ROOT_DIR}/source/static/scss:/app/scss:rw" \
	--volume "${HOST_ROOT_DIR}/source/static/js:/app/js:rw" \
	--env GIT_COMMIT_SHA \
	assets-builder:v1 \
	bash -c "npm run build"

When these commands complete successfully:

  • The SCSS files are bundled into a file called bundle.${GIT_COMMIT_SHA}.css
  • The JS files are bundled into a file called bundle.${GIT_COMMIT_SHA}.js

The package.json file is as follows:

{
  "name": "kosli",
  "version": "1.0.0",
  "description": "kosli css",
  "main": "index.js",
  "directories": { "doc": "docs" },
  "scripts": {
	"remove:assets": "rm -f scss/bundle.*.css js/bundle.*.js",
	"compile:css": "sass --no-source-map ... --style expanded",
	"prefix:css": "postcss ... -o scss/bundle.prefixed.css",
	"compress:css": "sass --no-source-map ... --style compressed",
	"cleanup:css": "rm scss/bundle.prefixed.css scss/bundle.css",
	"rename:css": "mv ... scss/bundle.${GIT_COMMIT_SHA}.css",
	"bundle:js": "bundle-js ... js/bundle.${GIT_COMMIT_SHA}.js",
	"build": "npm-run-all remove:assets ..."
  },
  ...
  "devDependencies": {
	"autoprefixer": "^10.4.12",
	"npm-run-all": "^4.1.5",
	"postcss": "^8.4.18",
	"postcss-cli": "^10.0.0",
	"sass": "^1.55.0",
	"bundle-js": "^1.0.3"
  }
}

The assets-builder:v1 docker image is created from the file Dockerfile.assets

FROM node
WORKDIR /app
RUN npm i sass postcss postcss-cli autoprefixer npm-run-all bundle-js --save-dev

using the bash command:

docker build --tag assets-builder:v1 -f Dockerfile.assets .

The server source has two functions to return the path to the pre-bundled files:

from flask import url_for
import os

def bundle_css():
	return url_for(‘static’, filename=fscss/bundle.{git_commit_sha()}.css”)

def bundle_js():
	return url_for(‘static’, filename=fjs/bundle.{git_commit_sha()}.js”)

def git_commit_sha():
	return os.environ.get("GIT_COMMIT_SHA")

These functions are called in the html head section:

<!DOCTYPE html>
<html>
  <head>
	<meta charset="UTF-8" />
	...
	<link rel="stylesheet" href="{{ bundle_css }}" />
	<script src="{{ bundle_js }}"></script>
  </head>
  <body>
	...
  </body>
</html>

Congratulations! You now know how to run a Python Flask server in a readonly Docker container.


ABOUT THIS ARTICLE

Published March 7, 2023, in technology

AUTHOR
Live in Git Blame? Don’t spend hours searching for the change that broke your application! Query, search and discover all the changes in one place

Sign up to our newsletter

We'll let you know about the Kosli launch, product news, features and updates
Kosli is committed to protecting and respecting your privacy. By submitting this newsletter request, I consent to Kosli sending me marketing communications via email. I may opt out at any time. For information about our privacy practices, please visit Kosli's privacy policy.
Kosli team reading the newsletter

Let’s chat!

Got a question about Kosli? An idea for a new feature? Join Kosli Slack and talk to us.

Join
Developers using Kosli