Skip to content

[FEATURE] Add Docker Setup and Documentation - #1023

Open
pulk17 wants to merge 1 commit into
CCExtractor:masterfrom
pulk17:feature/Docker
Open

[FEATURE] Add Docker Setup and Documentation#1023
pulk17 wants to merge 1 commit into
CCExtractor:masterfrom
pulk17:feature/Docker

Conversation

@pulk17

@pulk17 pulk17 commented Feb 16, 2026

Copy link
Copy Markdown
Contributor

[FEATURE]

In raising this pull request, I confirm the following (please check boxes):

  • I have read and understood the contributors guide.
  • I have checked that another pull request for this purpose does not exist.
  • I have considered, and confirmed that this submission will be valuable to others.
  • I accept that this submission may not be used, and the pull request closed at the will of the maintainer.
  • I give this submission freely, and claim no ownership to its content.

My familiarity with the project is as follows:

  • I am an active contributor to the project.

Docker development environment

Two containers — MySQL 8 and the application under Gunicorn — so a contributor
can get a working platform without installing MySQL, Python and the native
libraries the app needs:

cp env.example .env      # edit the passwords
docker compose up --build

That is the whole setup. The app comes up on http://localhost:5000 with a
schema, fixture data and a browsable UI.

Why the entrypoint doesn't just run flask db upgrade

It can't — and this is the part worth reviewing.

Replaying the migration chain against an empty database fails:

(1091, "Can't DROP 'regression_test_ibfk_1'; check that column/key exists")

Migration 2e0d2e02a721 drops foreign keys by the names MySQL generates
automatically. Those names only exist if the schema was first built from the
models, which is what database.create_session does on the app's first run.
So the chain assumes a database that was never created by the chain itself.
That is a pre-existing property of the repository, not something Docker
introduced, and it is why nothing here tries to "fix" the migrations.

install/init_schema.py therefore branches on whether alembic_version
exists:

  • absent — build the schema from the models and stamp() it at head,
    which is what a normal first run does anyway
  • present — an existing environment or a restored dump, so only apply the
    migrations that are newer

It deliberately never does both: create_all would create a table that a
pending migration still expects to create itself, which then fails.

A fresh database is also seeded with the existing install/sample_db.py,
because several pages dereference rows they assume are present — the home page
does GeneralData.query.filter(...).first().value with no null check, so an
empty schema alone serves a 500 on /. Seeding through the repository's own
script keeps that fix out of application code.

Files

File Purpose
Dockerfile Runtime image; deps cached ahead of the source copy
docker-compose.yml MySQL + app, app waits on the DB healthcheck
docker-entrypoint.sh Wait for DB → schema → Gunicorn
config.docker.py config.py for the container, all values from env
install/wait_for_db.py Block until MySQL accepts connections
install/init_schema.py Create-or-migrate, described above
install/generate_dev_credentials.py Throwaway service account so the GCS client can initialise offline
env.example, DOCKER.md, .dockerignore Configuration and docs

One change to application code

utility.pyserve_file_download unconditionally called
storage_client_bucket.blob(...), which cannot work without a real bucket, so
every download raised in development.

It now serves the file from SAMPLE_REPOSITORY when no bucket is configured.
A configured bucket still takes precedence and production behaviour is
unchanged
, with one exception worth naming: a GoogleAPIError from a
genuinely configured bucket now falls through to the local copy and returns
404 if it isn't there, where it previously raised. Happy to narrow that to
only the unconfigured case if you would rather production keep failing loudly.

Nothing else outside the Docker files is touched. The earlier
mod_sample/controllers.py edit from review has been dropped.

Security

  • Runs as an unprivileged user (uid 1001), not root
  • No secret in the repository — env.example ships placeholders, and secret
    keys plus dev GCP credentials are generated at image build
  • The MySQL port is not published to the host
  • Compose refuses to start if MYSQL_ROOT_PASSWORD or MYSQL_PASSWORD are
    unset rather than defaulting to something guessable
  • The root password stays with the database container; the app connects as an
    unprivileged user

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Comment thread mod_sample/controllers.py Outdated
}
raise SampleNotFoundException(f"Extra file {additional_id} for sample {sample.id} not found")
raise SampleNotFoundException(f"Sample with id {sample_id} not found")
raise SampleNotFoundException(f"Sample with id {sample_id} not found") No newline at end of file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this change please.

Comment thread .dockerignore Outdated
service-account.json
gcp-key.json
secret_key
secret_csrf No newline at end of file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a newline to the end.

Comment thread docker-compose.yml Outdated
@@ -0,0 +1,64 @@
services:
# --- 1. Database Service ---

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove all these unnecessary comments.

Comment thread docker-entrypoint.sh Outdated
Comment on lines +48 to +74
python3 -c "
import json
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization

try:
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
pem = key.private_bytes(serialization.Encoding.PEM,
serialization.PrivateFormat.TraditionalOpenSSL,
serialization.NoEncryption()).decode()
except Exception as e:
print(f'WARNING: Key generation failed: {e}')
pem = 'DUMMY_KEY'

sa = {
'type': 'service_account',
'project_id': 'docker-dev',
'private_key_id': 'docker-dev-key',
'private_key': pem,
'client_email': 'docker-dev@docker-dev.iam.gserviceaccount.com',
'client_id': '000000000000',
'auth_uri': 'https://accounts.google.com/o/oauth2/auth',
'token_uri': 'https://oauth2.googleapis.com/token',
}
with open('$REAL_SA_PATH', 'w') as f:
json.dump(sa, f, indent=2)
"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather see extra helper files (in the correct subfolder) than almost unreadable python in a shell script.

Comment thread docker-entrypoint.sh Outdated
@@ -0,0 +1,292 @@
#!/bin/bash

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is way too large for a entrypoint of a docker. A lot of these things should be handled at build time (ensuring dirs exist/created etc).

Comment thread Dockerfile Outdated
@@ -0,0 +1,81 @@
FROM python:3.11-slim-bullseye

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather see a more recent python version.

Comment thread env.example Outdated
# ============================================================

# ---------- MySQL ----------
MYSQL_ROOT_PASSWORD=root

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SP does not need to know the root password for MYSQL

Comment thread env.example Outdated
# Port exposed on the HOST for the Flask app (container always listens on 5000)
APP_PORT=5000
# Port exposed on the HOST for direct MySQL access (optional, for debugging)
DB_EXTERNAL_PORT=3306

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be exposed.

@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants