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.pywith 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 featuresapp/__init__.pywithcreate_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:
| Priority | Convention | Example |
|---|---|---|
| 1 | routes.py in the feature root | features/auth/routes.py |
| 2 | routes/{feature}_routes.py (screaming architecture) | features/auth/routes/auth_routes.py |
| 3 | Single .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:
- Creates a Flask app (via
create_app()if available, or manual fallback) - Registers all Flask Blueprints found in
routes.py - Optionally injects CORS headers via
@after_request - Calls any
init_apphooks declared indeployless.yaml - 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.