Skip to content

Deploying an AirTrail App

Introduction

AirTrail is an open-source flight logging application designed for pilots, aviation enthusiasts, and flight operations teams to track, manage, and analyze flight data. Built with modern web technologies, AirTrail provides a comprehensive platform for logging flights, managing aircraft information, tracking flight hours, and generating detailed flight reports.

AirTrail is renowned for its:

  • Flight Logging: Comprehensive flight log management with detailed flight entries
  • Aircraft Management: Track aircraft information, maintenance records, and flight history
  • Pilot Records: Maintain detailed pilot profiles and flight hour tracking
  • Reporting: Generate detailed flight reports and statistics
  • Data Export: Export flight data in various formats for record-keeping
  • User-Friendly Interface: Intuitive web interface for easy data entry and management
  • Multi-User Support: Support for multiple users and organizations
  • Data Security: Secure storage of sensitive aviation data
  • Search and Filter: Advanced search and filtering capabilities for flight records
  • Mobile-Friendly: Responsive design for access on various devices

Common use cases include flight schools, aviation clubs, private pilots, commercial flight operations, aircraft maintenance tracking, regulatory compliance reporting, and aviation data analysis.

This comprehensive guide walks you through deploying AirTrail on Klutch.sh using a Dockerfile, including detailed installation steps, sample code, persistent storage configuration, and production-ready best practices.

Prerequisites

Before you begin, ensure you have the following:

  • A Klutch.sh account
  • A GitHub account with a repository for your AirTrail project
  • Docker installed locally for testing (optional but recommended)
  • A PostgreSQL database (can be deployed separately on Klutch.sh or use an external database)
  • Node.js knowledge (helpful but not required)
  • Basic understanding of REST APIs and web applications

Installation and Setup

Step 1: Create Your Project Directory

First, create a new directory for your AirTrail deployment project:

Terminal window
mkdir airtrail-klutch
cd airtrail-klutch
git init

Step 2: Set Up AirTrail Application

If you’re starting with an existing AirTrail codebase, clone or copy it into your project directory. If you’re creating a new AirTrail application, you’ll need to set up the basic structure.

Create a package.json file for your AirTrail application:

{
"name": "airtrail",
"version": "1.0.0",
"description": "AirTrail flight logging application",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"build": "npm install --production"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.11.0",
"dotenv": "^16.3.1",
"cors": "^2.8.5",
"helmet": "^7.0.0",
"express-validator": "^7.0.1",
"jsonwebtoken": "^9.0.2",
"bcryptjs": "^2.4.3"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
}
}

Step 3: Create Sample Application Code

Create a basic AirTrail server application. Here’s a minimal example:

server.js (main application file):

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const { Pool } = require('pg');
require('dotenv').config();
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Database connection
const pool = new Pool({
host: process.env.DB_HOST,
port: process.env.DB_PORT || 5432,
database: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
ssl: process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false
});
// Health check endpoint
app.get('/health', async (req, res) => {
try {
await pool.query('SELECT 1');
res.json({ status: 'healthy', database: 'connected' });
} catch (error) {
res.status(503).json({ status: 'unhealthy', error: error.message });
}
});
// Get all flights
app.get('/api/flights', async (req, res) => {
try {
const result = await pool.query(
'SELECT * FROM flights ORDER BY flight_date DESC LIMIT 100'
);
res.json(result.rows);
} catch (error) {
console.error('Error fetching flights:', error);
res.status(500).json({ error: 'Failed to fetch flights' });
}
});
// Create a new flight log entry
app.post('/api/flights', async (req, res) => {
try {
const { aircraft_id, pilot_id, flight_date, departure_time, arrival_time,
origin, destination, flight_hours, notes } = req.body;
const result = await pool.query(
`INSERT INTO flights (aircraft_id, pilot_id, flight_date, departure_time,
arrival_time, origin, destination, flight_hours, notes, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW())
RETURNING *`,
[aircraft_id, pilot_id, flight_date, departure_time, arrival_time,
origin, destination, flight_hours, notes]
);
res.status(201).json(result.rows[0]);
} catch (error) {
console.error('Error creating flight:', error);
res.status(500).json({ error: 'Failed to create flight log' });
}
});
// Get flight statistics
app.get('/api/stats', async (req, res) => {
try {
const totalFlights = await pool.query('SELECT COUNT(*) FROM flights');
const totalHours = await pool.query('SELECT SUM(flight_hours) FROM flights');
const recentFlights = await pool.query(
'SELECT COUNT(*) FROM flights WHERE flight_date >= CURRENT_DATE - INTERVAL \'30 days\''
);
res.json({
total_flights: parseInt(totalFlights.rows[0].count),
total_hours: parseFloat(totalHours.rows[0].sum || 0),
recent_flights: parseInt(recentFlights.rows[0].count)
});
} catch (error) {
console.error('Error fetching statistics:', error);
res.status(500).json({ error: 'Failed to fetch statistics' });
}
});
// Start server
app.listen(PORT, '0.0.0.0', () => {
console.log(`AirTrail server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
});

Step 4: Create Database Schema

Create a SQL file for initializing the database schema:

schema.sql:

-- Create flights table
CREATE TABLE IF NOT EXISTS flights (
id SERIAL PRIMARY KEY,
aircraft_id INTEGER,
pilot_id INTEGER,
flight_date DATE NOT NULL,
departure_time TIME,
arrival_time TIME,
origin VARCHAR(10),
destination VARCHAR(10),
flight_hours DECIMAL(5,2),
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create aircraft table
CREATE TABLE IF NOT EXISTS aircraft (
id SERIAL PRIMARY KEY,
registration VARCHAR(20) UNIQUE NOT NULL,
make VARCHAR(50),
model VARCHAR(50),
year INTEGER,
total_hours DECIMAL(10,2) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create pilots table
CREATE TABLE IF NOT EXISTS pilots (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
license_number VARCHAR(50),
email VARCHAR(100),
total_hours DECIMAL(10,2) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_flights_date ON flights(flight_date);
CREATE INDEX IF NOT EXISTS idx_flights_aircraft ON flights(aircraft_id);
CREATE INDEX IF NOT EXISTS idx_flights_pilot ON flights(pilot_id);

Step 5: Create the Dockerfile

Create a Dockerfile in your project root directory. This will define your AirTrail container configuration:

FROM node:18-alpine
# Install build dependencies
RUN apk add --no-cache \
python3 \
make \
g++ \
postgresql-client
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy application files
COPY . .
# Create directory for persistent data
RUN mkdir -p /app/data && chown -R node:node /app/data
# Switch to non-root user
USER node
# Expose port
EXPOSE 3000
# Set environment variables
ENV NODE_ENV=production
ENV PORT=3000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
# Start the application
CMD ["node", "server.js"]

Note: This Dockerfile configures AirTrail to run on port 3000, which will be your internal port in Klutch.sh. The application uses Node.js 18 Alpine for a lightweight container.

Step 6: Create .env.example File

Create a .env.example file with required environment variables:

NODE_ENV=production
PORT=3000
# Database configuration
DB_HOST=your-database-host
DB_PORT=5432
DB_NAME=airtrail
DB_USER=your-db-user
DB_PASSWORD=your-db-password
DB_SSL=false
# Application settings
APP_NAME=AirTrail
APP_URL=https://example-app.klutch.sh
TZ=UTC

Step 7: Create .dockerignore File

Create a .dockerignore file to exclude unnecessary files from the Docker build:

node_modules
npm-debug.log
.env
.env.local
.git
.gitignore
README.md
.dockerignore
Dockerfile
docker-compose.yml

Step 8: Test Locally (Optional)

Before deploying to Klutch.sh, you can test your AirTrail setup locally:

Terminal window
# Build the Docker image
docker build -t my-airtrail .
# Run the container (assuming you have a PostgreSQL database running)
docker run -d \
--name airtrail-test \
-p 3000:3000 \
-e DB_HOST=host.docker.internal \
-e DB_PORT=5432 \
-e DB_NAME=airtrail \
-e DB_USER=postgres \
-e DB_PASSWORD=password \
-v $(pwd)/data:/app/data \
my-airtrail
# Check if the application is running
curl http://localhost:3000/health

Note: For local development with a database, you can use Docker Compose to run both AirTrail and PostgreSQL together. Docker Compose is only for local development; Klutch.sh does not support Docker Compose for deployment.

Step 9: Push to GitHub

Commit your AirTrail project files to your GitHub repository:

Terminal window
git add .
git commit -m "Initial AirTrail Docker setup for Klutch.sh"
git branch -M main
git remote add origin https://github.com/yourusername/airtrail-klutch.git
git push -u origin main

Deploying to Klutch.sh

Now that your AirTrail project is ready and pushed to GitHub, follow these steps to deploy it on Klutch.sh with persistent storage.

Deployment Steps

    1. Log in to Klutch.sh

      Navigate to klutch.sh/app and sign in to your account.

    2. Create a New Project

      Go to Create Project and give your project a meaningful name (e.g., “AirTrail Flight Logging”).

    3. Create a New App

      Navigate to Create App and configure the following settings:

    4. Select Your Repository

      • Choose GitHub as your Git source
      • Select the repository containing your Dockerfile
      • Choose the branch you want to deploy (usually main or master)

      Klutch.sh will automatically detect the Dockerfile in your repository root and use it for deployment.

    5. Configure Traffic Type

      • Traffic Type: Select HTTP (AirTrail is a web application)
      • Internal Port: Set to 3000 (the port your AirTrail container listens on, as defined in your Dockerfile)
    6. Set Environment Variables

      Add the following environment variables for your AirTrail configuration:

      • NODE_ENV: Set to production
      • PORT: Set to 3000 (matches the internal port)
      • DB_HOST: Your database host (if using a Klutch.sh PostgreSQL app, use the app URL like example-db.klutch.sh)
      • DB_PORT: Database port (for Klutch.sh TCP apps, use 8000 externally, but the internal port in your database app should be 5432 for PostgreSQL)
      • DB_NAME: Your database name (e.g., airtrail)
      • DB_USER: Database username
      • DB_PASSWORD: Database password
      • DB_SSL: Set to true if your database requires SSL connections
      • APP_NAME: Your application name (e.g., AirTrail)
      • APP_URL: Your Klutch.sh app URL (e.g., https://example-app.klutch.sh)
      • TZ: Your timezone (e.g., UTC or America/New_York)
    7. Attach Persistent Volumes

      AirTrail requires persistent storage for data files and logs:

      Data Volume:

      • Mount Path: /app/data
      • Size: Start with 5GB minimum (10GB+ recommended for production with many flight records)

      This volume stores:

      • Application data files
      • Log files
      • Exported flight reports
      • User uploads (if applicable)
      • Cache files

      Note: For production deployments with extensive flight logging, allocate sufficient storage. You can increase volume size later if needed.

    8. Configure Additional Settings

      • Region: Select the region closest to your users for optimal performance
      • Compute Resources: AirTrail has moderate resource requirements; allocate at least:
        • CPU: 1-2 cores recommended
        • Memory: 1GB minimum (2GB+ recommended for production workloads)
      • Instances: Start with 1 instance (you can scale horizontally later if needed)
    9. Deploy Your Application

      Click “Create” to start the deployment. Klutch.sh will:

      • Automatically detect your Dockerfile in the repository root
      • Build the Docker image
      • Attach the persistent volume(s)
      • Start your AirTrail container
      • Assign a URL for external access

      Note: The first deployment may take several minutes as it builds the Docker image and installs dependencies.

    10. Initialize Database

      After deployment, you’ll need to set up your AirTrail database schema. Connect to your PostgreSQL database and run the SQL schema from schema.sql, or use a database migration tool if your AirTrail application includes one.

    11. Access Your Application

      Once deployment is complete, you’ll receive a URL like example-app.klutch.sh. Visit this URL to access your AirTrail application.


Sample Code: Getting Started with AirTrail

Here are some examples to help you get started with AirTrail development:

Example 1: JavaScript Client - Fetching Flights

// Frontend JavaScript example for AirTrail API
async function fetchFlights() {
try {
const response = await fetch('https://example-app.klutch.sh/api/flights', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const flights = await response.json();
console.log('Flights:', flights);
return flights;
} catch (error) {
console.error('Error fetching flights:', error);
throw error;
}
}
// Display flights in the UI
async function displayFlights() {
const flights = await fetchFlights();
const container = document.getElementById('flights-container');
flights.forEach(flight => {
const flightElement = document.createElement('div');
flightElement.innerHTML = `
<h3>Flight ${flight.id}</h3>
<p>Date: ${flight.flight_date}</p>
<p>Route: ${flight.origin}${flight.destination}</p>
<p>Duration: ${flight.flight_hours} hours</p>
`;
container.appendChild(flightElement);
});
}

Example 2: Creating a Flight Log Entry

async function createFlightLog(flightData) {
try {
const response = await fetch('https://example-app.klutch.sh/api/flights', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
aircraft_id: flightData.aircraftId,
pilot_id: flightData.pilotId,
flight_date: flightData.date,
departure_time: flightData.departureTime,
arrival_time: flightData.arrivalTime,
origin: flightData.origin,
destination: flightData.destination,
flight_hours: flightData.hours,
notes: flightData.notes
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const newFlight = await response.json();
console.log('Flight created:', newFlight);
return newFlight;
} catch (error) {
console.error('Error creating flight log:', error);
throw error;
}
}
// Example usage
createFlightLog({
aircraftId: 1,
pilotId: 1,
date: '2024-01-15',
departureTime: '08:00:00',
arrivalTime: '10:30:00',
origin: 'KJFK',
destination: 'KLAX',
hours: 2.5,
notes: 'Training flight'
});

Example 3: Fetching Flight Statistics

async function getFlightStatistics() {
try {
const response = await fetch('https://example-app.klutch.sh/api/stats', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const stats = await response.json();
console.log('Flight Statistics:', stats);
// Display statistics
document.getElementById('total-flights').textContent = stats.total_flights;
document.getElementById('total-hours').textContent = stats.total_hours.toFixed(2);
document.getElementById('recent-flights').textContent = stats.recent_flights;
return stats;
} catch (error) {
console.error('Error fetching statistics:', error);
throw error;
}
}

Example 4: Python Client Example

import requests
import json
class AirTrailClient:
def __init__(self, base_url):
self.base_url = base_url
self.headers = {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
def get_flights(self):
"""Fetch all flights"""
response = requests.get(
f'{self.base_url}/api/flights',
headers=self.headers
)
response.raise_for_status()
return response.json()
def create_flight(self, flight_data):
"""Create a new flight log entry"""
response = requests.post(
f'{self.base_url}/api/flights',
headers=self.headers,
json=flight_data
)
response.raise_for_status()
return response.json()
def get_statistics(self):
"""Get flight statistics"""
response = requests.get(
f'{self.base_url}/api/stats',
headers=self.headers
)
response.raise_for_status()
return response.json()
# Example usage
client = AirTrailClient('https://example-app.klutch.sh')
# Fetch flights
flights = client.get_flights()
print(f"Total flights: {len(flights)}")
# Create a new flight
new_flight = client.create_flight({
'aircraft_id': 1,
'pilot_id': 1,
'flight_date': '2024-01-15',
'departure_time': '08:00:00',
'arrival_time': '10:30:00',
'origin': 'KJFK',
'destination': 'KLAX',
'flight_hours': 2.5,
'notes': 'Training flight'
})
print(f"Created flight: {new_flight['id']}")
# Get statistics
stats = client.get_statistics()
print(f"Total flight hours: {stats['total_hours']}")

Production Best Practices

Security Recommendations

  • Enable HTTPS: Always use HTTPS in production (Klutch.sh provides TLS certificates)
  • Secure Environment Variables: Store all sensitive credentials as environment variables in Klutch.sh
  • Database Security: Use strong database passwords and enable SSL connections
  • Input Validation: Always validate and sanitize user input before database operations
  • SQL Injection Prevention: Use parameterized queries (as shown in the sample code)
  • Rate Limiting: Implement rate limiting to prevent abuse
  • CORS Configuration: Configure CORS properly to restrict access to authorized domains
  • Helmet Security: The sample code includes Helmet for security headers
  • Regular Updates: Keep Node.js dependencies updated with security patches
  • Error Handling: Implement proper error handling without exposing sensitive information

Performance Optimization

  • Database Indexing: Ensure proper indexes on frequently queried columns (as shown in schema.sql)
  • Connection Pooling: Use connection pooling for database connections (PostgreSQL pool is configured in the sample code)
  • Caching: Implement caching for frequently accessed data
  • Query Optimization: Optimize database queries and avoid N+1 query problems
  • Compression: Enable gzip compression for API responses
  • CDN Integration: Serve static assets through a CDN if applicable
  • Monitoring: Set up monitoring and alerting for application performance

Monitoring and Maintenance

Monitor your AirTrail application for:

  • Application Logs: Check logs in Klutch.sh dashboard for errors
  • Database Performance: Monitor query performance and slow queries
  • Storage Usage: Monitor persistent volume usage and plan for growth
  • Response Times: Track API response times
  • Error Rates: Monitor 4xx and 5xx error rates
  • Resource Usage: Track CPU and memory usage in Klutch.sh dashboard

Regular maintenance tasks:

  • Backup Database: Regularly backup your PostgreSQL database
  • Backup Data Files: Backup data files from the persistent volume
  • Update Dependencies: Keep npm dependencies updated
  • Review Logs: Review application and error logs regularly
  • Security Audits: Perform regular security audits
  • Database Maintenance: Regularly run database maintenance tasks (VACUUM, ANALYZE)

Troubleshooting

Application Not Loading

  • Verify the app’s Traffic Type is HTTP
  • Check that the internal port is set to 3000 and matches your Dockerfile
  • Review build and runtime logs in the Klutch.sh dashboard
  • Ensure the Node.js application starts correctly (check the CMD in Dockerfile)

Database Connection Issues

  • Verify database environment variables are set correctly
  • For Klutch.sh PostgreSQL apps, use the app URL as the host and port 8000 externally
  • Check that the database is accessible from your AirTrail app
  • Verify database credentials and permissions
  • Ensure the database schema has been initialized

Health Check Failing

  • Verify the /health endpoint is accessible
  • Check database connectivity
  • Review application logs for errors
  • Ensure the database tables exist

Performance Issues

  • Review database query performance and add indexes if needed
  • Check resource allocation in Klutch.sh (CPU and memory)
  • Monitor database connection pool usage
  • Review application logs for slow operations
  • Consider implementing caching for frequently accessed data

Data Not Persisting

  • Ensure persistent volume is mounted at /app/data
  • Check file permissions on the persistent volume
  • Verify the application is writing to the correct directory
  • Ensure sufficient disk space in the persistent volume

  • Learn more about deploying applications on Klutch.sh in Deployments
  • Understand traffic types, ports, and routing in Networking
  • Explore how to work with storage in Volumes
  • Browse the full platform documentation at Klutch.sh Documentation
  • Learn about PostgreSQL deployment on Klutch.sh for your database needs

Conclusion

Deploying AirTrail to Klutch.sh with a Dockerfile provides a scalable, reliable flight logging solution with persistent storage, automatic deployments, and production-ready configuration. By following this guide, you’ve set up a high-performance AirTrail application with proper data persistence, security configurations, and the ability to handle real-world flight logging workloads.

AirTrail’s comprehensive flight logging capabilities, user-friendly interface, and robust data management make it an excellent choice for pilots, flight schools, and aviation organizations. Your application is now ready to track flights, manage aircraft, maintain pilot records, and generate detailed flight reports.

Remember to follow the production best practices outlined in this guide, regularly monitor your application performance, and adjust resources as your flight logging needs grow. With proper configuration, monitoring, and maintenance, AirTrail on Klutch.sh will provide a reliable, secure foundation for your aviation data management needs.