Skip to content

Project Structure Reference

Recommended project organization for Airbase applications

This document provides the reference for organizing your project files, configuration, and code for deployment to Airbase.


Overview

A well-structured project makes development, deployment, and maintenance easier. This reference covers standard patterns for different application types.

Key concepts: - Required files: Minimum files needed for deployment - Recommended structure: Best practices for organization - Framework conventions: Standard patterns per framework - Configuration location: Where to place config files


Required Files

Every Airbase project must include these files:

1. airbase.json

Location: Project root

Purpose: Airbase configuration

Required fields:

{
  "framework": "container",
  "handle": "namespace/project-name",
  "port": 3000,
  "instanceType": "b.small"
}

See: airbase.json Configuration for complete reference


2. Dockerfile

Location: Project root

Purpose: Container build instructions

Minimum requirements: - Use Airbase base image - Set USER app - Set appropriate file ownership (chown app:app) - Bind to $PORT environment variable - Expose port 3000

Example:

FROM gdssingapore/airbase:node-22
WORKDIR /app
COPY --chown=app:app . ./
USER app
EXPOSE 3000
CMD ["node", "index.js"]

See: Dockerfile Requirements for complete reference


3. .dockerignore

Location: Project root

Purpose: Exclude files from Docker build context

Recommended content:

node_modules
.git
.env*
*.log
.DS_Store
README.md
.vscode
.idea
coverage
dist
build


.env Files

Location: Project root

Purpose: Environment-specific configuration

Recommended files:

.env              # Production configuration
.env.staging      # Staging configuration
.env.development  # Local development (gitignored)
.env.example      # Template (committed to git)

See: Environment Variables Reference


.gitignore

Location: Project root

Purpose: Exclude files from version control

Recommended content:

# Dependencies
node_modules/
venv/
__pycache__/

# Environment variables
.env
.env.local
.env.development
.env.*.local

# Build outputs
dist/
build/
site/
*.pyc

# Logs
*.log
logs/

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/
*.swp
*.swo


README.md

Location: Project root

Purpose: Project documentation

Recommended sections:

# Project Name

## Description
Brief description of the application

## Prerequisites
- Node.js 22 / Python 3.13
- Docker

## Local Development
```bash
npm install
npm run dev

Deployment

airbase container build
airbase container deploy --yes staging

Environment Variables

See .env.example for required configuration

---

## Framework-Specific Structures

### Node.js/Express Application
my-express-app/ ├── airbase.json # Airbase configuration ├── Dockerfile # Container build ├── .dockerignore # Docker exclusions ├── .env # Production config ├── .env.staging # Staging config ├── .env.example # Config template ├── .gitignore # Git exclusions ├── package.json # Dependencies ├── package-lock.json # Locked dependencies ├── README.md # Documentation ├── src/ # Source code │ ├── index.js # Entry point │ ├── app.js # Express app │ ├── routes/ # Route handlers │ │ ├── index.js │ │ ├── users.js │ │ └── health.js │ ├── controllers/ # Business logic │ │ └── userController.js │ ├── models/ # Data models │ │ └── user.js │ ├── middleware/ # Express middleware │ │ └── auth.js │ ├── config/ # Configuration │ │ └── database.js │ └── utils/ # Utilities │ └── logger.js └── tests/ # Test files └── app.test.js
**Key conventions:**
- Entry point: `src/index.js` or `index.js`
- Port: Read from `process.env.PORT`
- Health check: `/health` endpoint
- Separate routes, controllers, models

**Example index.js:**
```javascript
const app = require('./app');
const PORT = process.env.PORT || 3000;

app.listen(PORT, '0.0.0.0', () => {
  console.log(`Server running on port ${PORT}`);
});


Python/Flask Application

my-flask-app/
├── airbase.json          # Airbase configuration
├── Dockerfile            # Container build
├── .dockerignore         # Docker exclusions
├── .env                  # Production config
├── .env.staging          # Staging config
├── .env.example          # Config template
├── .gitignore            # Git exclusions
├── requirements.txt      # Dependencies
├── README.md             # Documentation
├── app.py                # Entry point (or wsgi.py)
├── config.py             # Configuration
├── app/                  # Application package
│   ├── __init__.py       # App factory
│   ├── routes/           # Route blueprints
│   │   ├── __init__.py
│   │   ├── main.py
│   │   └── api.py
│   ├── models/           # Data models
│   │   ├── __init__.py
│   │   └── user.py
│   ├── services/         # Business logic
│   │   ├── __init__.py
│   │   └── user_service.py
│   └── utils/            # Utilities
│       ├── __init__.py
│       └── logger.py
└── tests/                # Test files
    └── test_app.py

Key conventions: - Entry point: app.py or wsgi.py - Port: Read from os.environ.get('PORT') - Health check: /health endpoint - Use blueprints for routes - Package structure with __init__.py

Example app.py:

import os
from app import create_app

app = create_app()
PORT = int(os.environ.get('PORT', 3000))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=PORT)


Python/Streamlit Application

my-streamlit-app/
├── airbase.json          # Airbase configuration
├── Dockerfile            # Container build
├── .dockerignore         # Docker exclusions
├── .env                  # Production config
├── .env.staging          # Staging config
├── .env.example          # Config template
├── .gitignore            # Git exclusions
├── requirements.txt      # Dependencies
├── README.md             # Documentation
├── main.py               # Streamlit entry point
├── config.py             # Configuration
├── pages/                # Multi-page app pages
│   ├── 1_📊_Dashboard.py
│   ├── 2_📈_Analytics.py
│   └── 3_⚙️_Settings.py
├── utils/                # Utility functions
│   ├── __init__.py
│   ├── data_loader.py
│   └── visualizations.py
├── data/                 # Static data files
│   └── sample.csv
└── tests/                # Test files
    └── test_utils.py

Key conventions: - Entry point: main.py or app.py - Multi-page apps: pages/ directory with numbered files - Utilities in utils/ directory - Static data in data/ directory

Example main.py:

import streamlit as st
import os

# Configuration
st.set_page_config(
    page_title="My Dashboard",
    page_icon="📊",
    layout="wide"
)

# Main app
st.title("My Streamlit Dashboard")
st.write("Welcome to my application")

Dockerfile CMD:

CMD ["sh", "-c", "streamlit run main.py --server.port=${PORT:-8501} --server.address=0.0.0.0"]


Static Site (React/Vite)

my-react-app/
├── airbase.json          # Airbase configuration
├── Dockerfile            # Multi-stage build
├── .dockerignore         # Docker exclusions
├── .gitignore            # Git exclusions
├── package.json          # Dependencies
├── package-lock.json     # Locked dependencies
├── README.md             # Documentation
├── nginx.conf            # Nginx configuration
├── vite.config.ts        # Vite configuration
├── tsconfig.json         # TypeScript configuration
├── index.html            # HTML entry point
├── public/               # Static assets
│   ├── favicon.ico
│   └── robots.txt
└── src/                  # Source code
    ├── main.tsx          # Entry point
    ├── App.tsx           # Root component
    ├── components/       # React components
    │   ├── Header.tsx
    │   └── Footer.tsx
    ├── pages/            # Page components
    │   ├── Home.tsx
    │   └── About.tsx
    ├── styles/           # CSS/SCSS
    │   └── main.css
    ├── utils/            # Utilities
    │   └── api.ts
    └── assets/           # Images, fonts
        └── logo.svg

Key conventions: - Multi-stage Dockerfile (builder + Nginx) - Build output served by Nginx - Client-side routing with try_files - CSP-compliant Vite configuration

nginx.conf:

server {
  listen 3000;
  root /usr/share/nginx/html;
  index index.html;

  # SPA fallback
  location / {
    try_files $uri $uri/ /index.html;
  }

  # Cache static assets
  location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }
}


Configuration File Locations

airbase.json

Location: Project root

Contents:

{
  "framework": "container",
  "handle": "namespace/project-name",
  "port": 3000,
  "instanceType": "b.small"
}


Environment Variables

Location: Project root

Files: - .env - Production - .env.staging - Staging - .env.development - Local development - .env.example - Template (commit this)

Never commit: .env, .env.staging, .env.development


Dockerfile

Location: Project root

Name: Must be exactly Dockerfile (capital D)


Package Dependencies

Node.js: - package.json - Project root - package-lock.json - Project root (commit this)

Python: - requirements.txt - Project root


Directory Naming Conventions

Source Code

Node.js: - src/ - Main source code - lib/ - Alternative to src/ - dist/ or build/ - Build output (gitignore these)

Python: - app/ - Application package - src/ - Alternative source directory - __pycache__/ - Python cache (gitignore this)

Static Sites: - src/ - Source code - public/ - Static assets - dist/ or build/ - Build output (gitignore this)


Tests

Common patterns: - tests/ - All test files - test/ - Alternative - __tests__/ - Jest convention (Node.js) - *.test.js or *.spec.js - Test files


Configuration

Common patterns: - config/ - Configuration files - .env files - Project root - *.config.js - Build tool configs (root)


Best Practices

✅ Do's

1. Keep configuration at root:

✅ Good:
my-app/
├── airbase.json
├── Dockerfile
├── .env
└── src/

❌ Bad:
my-app/
└── config/
    ├── airbase.json
    ├── Dockerfile
    └── .env

2. Use standard directory names: - src/ for source code - tests/ for tests - public/ for static assets

3. Separate concerns:

src/
├── routes/       # HTTP routes
├── controllers/  # Business logic
├── models/       # Data models
├── utils/        # Utilities
└── config/       # Configuration

4. Use .dockerignore:

node_modules
.git
.env*
*.log

5. Provide .env.example:

# .env.example (commit this)
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=your-api-key-here
LOG_LEVEL=info


❌ Don'ts

1. Don't commit secrets:

❌ Bad - committed .env:
.env  # Contains production secrets

✅ Good - gitignored .env:
.env.example  # Template only

2. Don't nest configuration too deep:

❌ Bad:
my-app/
└── config/
    └── deployment/
        └── airbase/
            └── airbase.json

✅ Good:
my-app/
└── airbase.json

3. Don't mix source and build output:

❌ Bad:
src/
├── app.js
└── dist/
    └── app.bundle.js

✅ Good:
src/
└── app.js
dist/  # Separate top-level
└── app.bundle.js

4. Don't use inconsistent naming:

❌ Bad:
my-app/
├── Sources/
├── Tests/
├── Utils/
└── config/

✅ Good (consistent lowercase):
my-app/
├── src/
├── tests/
├── utils/
└── config/


Examples

Minimal Node.js API

minimal-api/
├── airbase.json
├── Dockerfile
├── .dockerignore
├── .env.example
├── .gitignore
├── package.json
├── package-lock.json
└── index.js

Use case: Simple REST API with no complex structure


Standard Python Web App

standard-flask-app/
├── airbase.json
├── Dockerfile
├── .dockerignore
├── .env.example
├── .gitignore
├── requirements.txt
├── app.py
├── config.py
├── app/
│   ├── __init__.py
│   ├── routes/
│   ├── models/
│   └── utils/
└── tests/

Use case: Flask application with blueprints


Production React SPA

production-react-spa/
├── airbase.json
├── Dockerfile
├── .dockerignore
├── .gitignore
├── nginx.conf
├── package.json
├── vite.config.ts
├── tsconfig.json
├── index.html
├── public/
└── src/
    ├── main.tsx
    ├── App.tsx
    ├── components/
    ├── pages/
    ├── utils/
    └── styles/

Use case: Production-ready React SPA with TypeScript


Migration from Other Platforms

From Heroku

Changes needed: 1. Add airbase.json 2. Update Dockerfile to use Airbase base images 3. Change port binding from process.env.PORT || 3000 to process.env.PORT (required) 4. Add health check endpoint at /health 5. Ensure USER app in Dockerfile

Procfile not used - Use Dockerfile CMD instead


From Vercel/Netlify

Changes needed: 1. Add airbase.json 2. Create multi-stage Dockerfile (builder + Nginx) 3. Add nginx.conf for serving 4. Configure SPA routing with try_files 5. Ensure CSP-compliant build (external scripts only)

Serverless functions not supported - Use backend API instead


From Docker Compose

Changes needed: 1. Add airbase.json 2. Use Airbase base images 3. Update Dockerfile with USER app 4. Use .env files instead of docker-compose.yml environment 5. Database/Redis must be external services (not in compose)

Multi-container not supported - Deploy each service separately


Troubleshooting

"File not found" during build

Problem: Dockerfile cannot find source files

Solution: 1. Check .dockerignore is not excluding source files 2. Verify COPY paths are correct 3. Ensure files exist in repository


"Permission denied" errors

Problem: Files not accessible to app user

Solution:

# Use --chown in COPY commands
COPY --chown=app:app . ./

# Ensure USER app is set
USER app


Environment variables not loading

Problem: .env file not found

Solution: 1. Ensure .env or .env.staging exists in project root 2. Check file names match environment (.env.staging for staging) 3. Verify .env not in .gitignore (it should be, use deployment-specific .env)


See Also