gapps turns ten security frameworks into a control tracker your team can update
Security compliance platform - SOC2, CMMC, ASVS, ISO27001, HIPAA, NIST CSF, NIST 800-53, CSC CIS 18, PCI DSS, SSF tracking
At a glance
- What is it?
- bmarsh9/gapps is a self-hosted Flask and Postgres application for tracking progress against SOC2, NIST CSF, NIST 800-53, CMMC, HIPAA, ASVS, ISO27001, CSC CIS18, PCI DSS and SSF, with multi-tenancy, OIDC single sign-on, auditor collaboration, a risk register and file storage on S3 or GCS. Frameworks load from JSON files, so the control definitions can live in version control instead of a UI form.
- Who is it for?
- Adopt gapps if you are running a compliance programme across several frameworks and want a self-hosted place to track control status, with framework definitions imported from JSON so they can be reviewed like code, and with a documented path for migrations, upgrades and resets.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 155 days ago.
- What is it written in?
- Mainly HTML, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Ten frameworks, one set of controls and subcontrols
Gapps is described as a security compliance platform that makes it easy to track your progress against various security frameworks. The frameworks are listed in the README's new features section: SOC2, NIST CSF, NIST 800-53, CMMC, HIPAA, ASVS, ISO27001, CSC CIS18, PCI DSS and SSF, which the project counts as ten in total. Alongside the frameworks come the platform features that a compliance programme actually needs: multi-tenancy, OIDC for single sign-on, collaboration with auditors, a risk register, and file storage on S3 or GCS. Integrations are listed as the next big feature. The audience is a team that has to answer the same question across several frameworks at once, because the work of proving a control for one framework is rarely wasted for another, and the value of the tool is in tracking that shared work rather than in the framework definitions themselves.
The application is Flask, SQLAlchemy and Postgres
The stack is visible in the requirements file and tells you the shape of the project. Flask 2.2.5 with Flask-Login, Flask-Migrate, Flask-Mail, Flask-Script and Flask-SQLAlchemy on top of SQLAlchemy 1.3.20, with alembic 1.4.3 for migrations and psycopg2 as the Postgres driver, served by gunicorn 20.1.0. Around that sit the pieces a multi-tenant deployment needs: authlib for OIDC, boto3 and google-cloud-storage for file storage, google-cloud-logging, shortuuid for identifiers, parsedatetime and arrow for dates, and pytz. The container image is built from python:3.9-slim in two stages, with build dependencies such as libpq-dev and gcc in the builder and only libpq5 in the final image. The compose file defines two services on two bridge networks, an app service publishing 8000 to 5000 and a postgres service publishing 5432, with the app depending on the database and both set to restart unless stopped.
Environment variables are the entire configuration surface
There is no configuration file to learn. The compose file passes the database credentials in as environment variables with defaults, and the application reads them the same way. The documented set for a container run is:
export POSTGRES_HOST=${POSTGRES_HOST:-postgres}
export POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-db1}
export POSTGRES_USER=${POSTGRES_USER:-db1}
export POSTGRES_DB=${POSTGRES_DB:-db1}
export SQLALCHEMY_DATABASE_URI="postgresql://db1:db1@postgres/db1"The defaults are deliberately obvious, with db1 as the username, database and password, and postgres as the host inside the compose network. The compose file adds RESET_DB, VERSION and GUNICORN_WORKERS, with two workers as the default, and it keeps a commented out env_file entry pointing at a .env file for people who prefer that. For a development run outside Docker the same variables are exported with the host set to localhost instead:
export POSTGRES_HOST=${POSTGRES_HOST:-localhost}
export POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-db1}
export POSTGRES_USER=${POSTGRES_USER:-db1}
export POSTGRES_DB=${POSTGRES_DB:-db1}
export SQLALCHEMY_DATABASE_URI="postgresql://db1:db1@localhost/db1"The first connection error is documented, with the fix
The FAQ opens with the error you will meet if the environment variables point at the wrong place:
[INFO] Checking if we can connect to the database server: postgresql://db1:db1@localhost/db1
[ERROR] could not connect to server: Connection refused
Is the server running on host "localhost" (127.0.0.1) and accepting
TCP/IP connections on port 5432?
could not connect to server: Cannot assign requested address
Is the server running on host "localhost" (::1) and accepting
TCP/IP connections on port 5432?The README says it can usually be fixed by unsetting two variables when running within Docker, because the URI baked into the environment is overriding the host the container actually uses:
unset SQLALCHEMY_DATABASE_URI
unset POSTGRES_HOSTThat is a small thing to document and a large thing to save an afternoon, and it is typical of how this README is written. The same section explains that a first start creates the database models automatically, that a reset can be forced with the RESET_DB variable set to yes, and that for development you start the Postgres container and then the application in the foreground with the FLASK_CONFIG variable set to development and run.sh executed. There is also a debugging one-liner that dumps the container environment, using the ONESHOT flag so the container exits after printing.
Frameworks arrive as JSON, not as a form
This is the design decision that separates gapps from a checklist tool. You can always create a framework and its controls through the user interface, but the README says that would take a long time, and instead you load a JSON file. The format is a list of controls, each with a name, description, guidance, reference code, a system level flag, subcategory, category, dti and dtc difficulty fields, a meta object and a list of subcontrols, and a subcontrol can carry a list of tasks. The example uses a CIS 18 control, where the eighteen domains would be controls and the controls inside each domain would be subcontrols. A control does not need subcontrols, and the guidance is that breaking a control into trackable actions is often the useful choice:
[
{
"name": "Limit information system access to authorized users, processes acting on behalf of authorized users or",
"description": "Maintain list of authorized users defining their identity and associated role and sync with sys",
"guidance": "List approved users, services, and devices, and have logical controls in place to prevent unauthor",
"ref_code": "AC.L1-3.1.1",
"system_level": false,
"subcategory": "Identity & Access Management (IAM)",
"category": "Access Control",
"dti": "easy",
"dtc": "easy",
"meta": {},The file name becomes the framework name, the file has to end in .json, and it goes into the app/files/base_controls/ directory, or wherever the FRAMEWORK_FOLDER environment variable points. The practical effect is that the definition of a framework is a reviewable artefact in your own repository.
Migrations, resets and version upgrades are three different jobs
The README keeps these apart, which is worth reading carefully before an upgrade. A database migration is run through the container with three flags set:
docker exec -e INIT_MIGRATE=yes -e MIGRATE=yes -e ONESHOT=yes gapps bash run.shThe alternative path goes through the container's shell and the Flask-Migrate commands:
docker-compose up -d
docker exec -it gapps bash
python3 manage.py db migrate
python3 manage.py db stamp head
python3 manage.py db upgrade
exitCreating the database by hand is a third path, and it is destructive, with the README warning that it deletes all data in the database:
docker exec -it gapps bash
python3 tools/check_db_connection.py
python3 tools/check_db_models.py
python3 manage.py init_dbFor a version upgrade the procedure is to edit the docker-compose.yml file with the desired version from Docker Hub, where the README notes the old version appears in four places, then run docker-compose up -d, and perform a database migration if necessary. Versions are published as images under the bmarsh13/gapps name.
What it costs you to run and what it does not do
Adopting gapps means adopting its dependencies. The requirements file pins Flask-SQLAlchemy 2.5.1 with SQLAlchemy 1.3.20, and it also pulls in Flask-Script 2.0.5, a project that has been unmaintained for years, so expect to keep an eye on that when you upgrade. The Python version is 3.9 in the image, which is no longer the newest. Storage is Postgres, and the compose file runs the app in four instances, so the deployment is not a single process you can copy onto a small box. The licence is another thing to check, because GitHub could not classify it and points you to the LICENSE file, so read that before you fork it into a company product. The repository has no GitHub releases, and the last push was on 2026-05-20, so the code you deploy should come from a tagged image rather than from a build of the branch.
The alternative is a spreadsheet, and the difference is who updates it
The realistic alternative to a platform like this is a spreadsheet per framework, and the difference in approach is not the control list, which can be exported either way, but the update path. In a spreadsheet, the status of a control lives in a file that someone edits and nobody else can see, progress is a manual roll-up, and an auditor asking what changed since last quarter means diffing documents. In gapps, the status lives in a shared database behind a login, the framework definitions come from JSON files that can sit in a repository and be reviewed like code, and the multi-tenancy and OIDC features exist so that more than one team, and an external auditor, can be given access. If you are one person with one framework, the spreadsheet wins on effort. If you are a team with a deadline and an auditor who will ask for evidence, the platform earns its setup cost, and the JSON import is the part that makes the definitions themselves cheap to maintain.
Editorial conclusion
Adopt gapps if you are running a compliance programme across several frameworks and want a self-hosted place to track control status, with framework definitions imported from JSON so they can be reviewed like code, and with a documented path for migrations, upgrades and resets. Do not adopt it if you need a tool that runs without a database or without Docker, because the application is a Flask service backed by Postgres and shipped as a compose file, and do not treat it as evidence that you are compliant, since it records progress rather than auditing it. Before you deploy, read the LICENSE file, because the repository licence is not one GitHub could classify, and pin a release image from Docker Hub rather than tracking the branch.
Frequently asked questions
Which compliance frameworks does gapps support?
The README lists ten: SOC2, NIST CSF, NIST 800-53, CMMC, HIPAA, ASVS, ISO27001, CSC CIS18, PCI DSS and SSF. New frameworks can also be created in the user interface or loaded from a JSON file, which is much faster than typing the controls in.
How do I load a new framework into gapps?
Write the framework as a JSON file whose name ends in .json, containing controls with subcontrols and tasks, and save it in the app/files/base_controls/ directory, or change the load directory with the FRAMEWORK_FOLDER environment variable. The framework name is taken from the filename.
How do I run gapps locally for development?
Uncomment the ports declaration in the compose file, start the Postgres container with docker-compose up -d postgres, export the POSTGRES and SQLALCHEMY_DATABASE_URI variables with the host set to localhost, then run export FLASK_CONFIG=development and bash run.sh so the application runs in the foreground.
How do I reset the gapps database?
Set the RESET_DB environment variable, for example with export RESET_DB=yes, which the README describes as deleting all data and recreating it. The first start of the application creates the database models automatically in any case.
How do I upgrade gapps to a newer version?
Edit the docker-compose.yml file with the desired version from the Docker Hub tags, updating the old version wherever it appears, which the README says is four places, then run docker-compose up -d and perform a database migration if needed. The migration is run with docker exec setting INIT_MIGRATE, MIGRATE and ONESHOT before executing run.sh.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bmarsh9-gapps)Was this useful?