Kosli raises $3.1 M USD in seed funding backed by Heavybit - Read more
Do you struggle to track Terraform changes? In a constant battle against drift? - Learn how to track Terraform with Kosli 1pm GMT 8/2/24
python flask docker

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

Jon Jagger
Author Jon Jagger
Published March 7, 2023 in technology
clock icon 3 min read

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

Stay in the loop with the Kosli newsletter

Get the latest updates, tutorials, news and more, delivered right to your inbox
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

Got a question about Kosli?

We’re here to help, our customers range from larges fintechs, medtechs and regulated business all looking to streamline their DevOps audit trails

Contact us
Developers using Kosli