Skip to main content

Project Structure

deployless expects a feature-based directory structure. Each feature directory with a routes.py becomes its own Lambda function.

Directory layout​

my-project/
├── deployless.yaml # Project config (created by deployless init)
├── requirements.txt # Global dependencies
├── run.py # Local development: python run.py
├── .gitignore
├── app/
│ ├── __init__.py # App factory: create_app() → Flask
│ ├── features/ # One folder per feature = one Lambda per feature
│ │ ├── hello/
│ │ │ └── routes.py # REQUIRED — Flask Blueprint with routes
│ │ ├── users/
│ │ │ ├── routes.py # Blueprint + dpl.configure() + resources
│ │ │ ├── use_cases/ # Business logic (optional subdirectories)
│ │ │ ├── repositories/
│ │ │ ├── schemas/
│ │ │ └── requirements.txt # Optional — extra deps for this feature only
│ │ └── orders/
│ │ └── routes.py
│ └── shared/ # Shared code — copied into ALL Lambdas
│ ├── decorators/
│ ├── errors/
│ └── config.py

Key rules​

  • Each feature must have a routes.py with at least one Flask Blueprint
  • Features cannot import from each other. Each feature is packaged into its own Lambda and has no access to other features' code
  • app/shared/ is copied into every Lambda — use it for code shared across features
  • app/__init__.py with create_app() is recommended — deployless uses it automatically for CORS, error handlers, middleware, etc.
  • Directories starting with _ (e.g. __pycache__) are ignored during discovery

Entry point resolution​

deployless tries these conventions in order:

PriorityConventionExample
1routes.py in the feature rootfeatures/auth/routes.py
2routes/{feature}_routes.py (screaming architecture)features/auth/routes/auth_routes.py
3Single .py file in routes/features/auth/routes/handler.py

Generated output​

After deployless build, a .dist/ directory is created:

.dist/                           # Do not commit to git
├── AuthFunction/
│ ├── app/
│ │ ├── __init__.py
│ │ ├── features/
│ │ │ ├── __init__.py
│ │ │ └── auth/ # Only this feature's code
│ │ │ ├── routes.py
│ │ │ ├── use_cases/
│ │ │ └── ...
│ │ └── shared/ # Copy of app/shared/
│ ├── bootstrap.py # Auto-generated entry point
│ ├── deployless.py # Runtime stub (no-ops)
│ └── requirements.txt # Global + feature + aws-wsgi
├── UserFunction/
└── TenantFunction/

The generated bootstrap​

For each Lambda a bootstrap.py is generated that:

  1. Creates a Flask app (via create_app() if available, or manual fallback)
  2. Registers all Flask Blueprints found in routes.py
  3. Optionally injects CORS headers via @after_request
  4. Calls any init_app hooks declared in deployless.yaml
  5. Wraps the app with aws_wsgi.response() to convert API Gateway events into WSGI requests

For multi-feature projects, deployless generates minimal stubs for other features so create_app() can import all blueprints without errors — without including their actual code in the package.

Runtime stub​

Each Lambda includes a deployless.py with no-op implementations of all deployless functions (configure, KMS, DynamoDB, etc.), so that import deployless as dpl statements in routes.py do not fail at runtime.