Fix Supervisor start error in containerized environment

- Enhanced database connection with multiple fallback paths
- Updated configuration to use environment variables
- Fixed Alembic migrations to work with dynamic database path
- Improved robustness of database initialization
- Added Docker deployment instructions to README
- Updated environment variables documentation
This commit is contained in:
Automated Action 2025-06-03 01:29:32 +00:00
parent 84555ef4e8
commit d024248fe9
6 changed files with 124 additions and 28 deletions

View File

@ -104,6 +104,30 @@ uvicorn main:app --host 0.0.0.0 --port 8000 --reload
The API will be available at [http://localhost:8000](http://localhost:8000)
### Running with Docker
You can also run the application using Docker:
```bash
# Build the Docker image
docker build -t hrplatform-backend .
# Run the container
docker run -p 8000:8000 -e DATABASE_PATH=/app/storage/db/db.sqlite hrplatform-backend
```
For production deployments, make sure to set the necessary environment variables:
```bash
docker run -p 8000:8000 \
-e SECRET_KEY="your-secure-key" \
-e FIRST_SUPERUSER="admin@yourdomain.com" \
-e FIRST_SUPERUSER_PASSWORD="secure-password" \
-e DATABASE_PATH="/app/storage/db/db.sqlite" \
-v /host/path/to/data:/app/storage \
hrplatform-backend
```
## API Documentation
Once the server is running, you can access the interactive API documentation:
@ -168,7 +192,15 @@ Once the server is running, you can access the interactive API documentation:
| ACCESS_TOKEN_EXPIRE_MINUTES| JWT token expiration time in minutes | 11520 (8 days) |
| FIRST_SUPERUSER | Email for the initial admin user | admin@example.com |
| FIRST_SUPERUSER_PASSWORD | Password for the initial admin user | admin123 |
| SQLALCHEMY_DATABASE_URI | Database connection URI | sqlite:////app/storage/db/db.sqlite |
| DATABASE_PATH | Path to the SQLite database file | Auto-detected with fallbacks |
The application will automatically try the following paths for the database (in order):
1. Path specified in the DATABASE_PATH environment variable (if set)
2. /app/storage/db/db.sqlite (standard Docker container path)
3. /tmp/hrplatform/db/db.sqlite (fallback for limited permissions)
4. ./db/db.sqlite (relative to the current working directory)
This makes the application work reliably in different environments, including Docker containers, without requiring manual configuration.
## License

View File

@ -35,8 +35,8 @@ script_location = migrations
# are written from script.py.mako
# output_encoding = utf-8
# SQLite URL example
sqlalchemy.url = sqlite:////app/storage/db/db.sqlite
# SQLite URL example - this will be overridden in env.py
sqlalchemy.url = sqlite:///db/db.sqlite
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run

View File

@ -1,7 +1,8 @@
from typing import List, Optional
from pydantic_settings import BaseSettings
from pydantic import validator, EmailStr
from pydantic import EmailStr
import secrets
import os
class Settings(BaseSettings):
@ -9,29 +10,23 @@ class Settings(BaseSettings):
API_V1_STR: str = "/api/v1"
# SECURITY
SECRET_KEY: str = secrets.token_urlsafe(32)
SECRET_KEY: str = os.environ.get("SECRET_KEY", secrets.token_urlsafe(32))
# 60 minutes * 24 hours * 8 days = 8 days
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES", 60 * 24 * 8))
# CORS
BACKEND_CORS_ORIGINS: List[str] = ["*"]
# First superuser
FIRST_SUPERUSER: EmailStr = "admin@example.com"
FIRST_SUPERUSER_PASSWORD: str = "admin123"
FIRST_SUPERUSER: EmailStr = os.environ.get("FIRST_SUPERUSER", "admin@example.com")
FIRST_SUPERUSER_PASSWORD: str = os.environ.get("FIRST_SUPERUSER_PASSWORD", "admin123")
# SQLITE DB
# Database path - this is just a placeholder, the actual path is determined in db/session.py
DATABASE_PATH: Optional[str] = os.environ.get("DATABASE_PATH")
# SQLITE DB URI - will be set by db/session.py
SQLALCHEMY_DATABASE_URI: Optional[str] = None
@validator("SQLALCHEMY_DATABASE_URI", pre=True)
def assemble_db_connection(cls, v: Optional[str], values: dict) -> str:
if v:
return v
from pathlib import Path
DB_DIR = Path("/app") / "storage" / "db"
DB_DIR.mkdir(parents=True, exist_ok=True)
return f"sqlite:///{DB_DIR}/db.sqlite"
class Config:
case_sensitive = True
env_file = ".env"

View File

@ -1,13 +1,44 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from pathlib import Path
import os
# Try to get database path from environment variable
DB_PATH = os.environ.get("DATABASE_PATH")
DB_DIR = Path("/app") / "storage" / "db"
DB_DIR.mkdir(parents=True, exist_ok=True)
SQLALCHEMY_DATABASE_URL = f"sqlite:///{DB_DIR}/db.sqlite"
if DB_PATH:
# Use the provided path
SQLALCHEMY_DATABASE_URL = f"sqlite:///{DB_PATH}"
else:
# Default paths with fallbacks for different environments
possible_paths = [
Path("/app/storage/db/db.sqlite"), # Docker container standard path
Path("/tmp/hrplatform/db/db.sqlite"), # Fallback to tmp directory
Path.cwd() / "db" / "db.sqlite" # Local development in current directory
]
# Find the first parent directory that is writable
for path in possible_paths:
try:
# Ensure directory exists
path.parent.mkdir(parents=True, exist_ok=True)
# Test if we can write to this directory
test_file = path.parent / ".write_test"
test_file.touch()
test_file.unlink()
SQLALCHEMY_DATABASE_URL = f"sqlite:///{path}"
print(f"Using database at: {path}")
break
except (PermissionError, OSError):
continue
else:
# If we get here, none of the paths worked
raise RuntimeError(
"Could not find a writable location for the database. "
"Please set the DATABASE_PATH environment variable."
)
# Create the engine with the configured URL
engine = create_engine(
SQLALCHEMY_DATABASE_URL,
connect_args={"check_same_thread": False}

View File

@ -16,6 +16,12 @@ sys.path.append(str(BASE_DIR))
# access to the values within the .ini file in use.
config = context.config
# Import database configuration to get the right database URL
from app.db.session import SQLALCHEMY_DATABASE_URL # noqa: E402
# Override the SQLAlchemy URL with our dynamically determined one
config.set_main_option("sqlalchemy.url", SQLALCHEMY_DATABASE_URL)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
@ -25,7 +31,7 @@ if config.config_file_name is not None:
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from app.db.base import Base # noqa
from app.db.base import Base # noqa: E402
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,

View File

@ -9,6 +9,7 @@ Usage:
python run_migrations.py --help
"""
import os
import sys
from pathlib import Path
@ -17,9 +18,40 @@ BASE_DIR = Path(__file__).resolve().parent
sys.path.append(str(BASE_DIR))
print(f"Added {BASE_DIR} to Python path")
if __name__ == "__main__":
# Import alembic's main function
from alembic.config import main
# Set environment variables for containerized environments if not already set
if "DATABASE_PATH" not in os.environ:
# Try multiple possible paths for the database
possible_paths = [
"/app/storage/db/db.sqlite",
"/tmp/hrplatform/db/db.sqlite",
str(BASE_DIR / "db" / "db.sqlite")
]
for path in possible_paths:
try:
# Ensure directory exists
db_dir = Path(path).parent
db_dir.mkdir(parents=True, exist_ok=True)
# Test if we can write to this directory
test_file = db_dir / ".write_test"
test_file.touch()
test_file.unlink()
os.environ["DATABASE_PATH"] = path
print(f"Using database at: {path}")
break
except (PermissionError, OSError):
continue
else:
print("WARNING: Could not find a writable location for the database.")
print("Please set the DATABASE_PATH environment variable.")
# Execute alembic command with sys.argv (e.g., 'upgrade', 'head')
main(argv=sys.argv[1:])
if __name__ == "__main__":
try:
# Import alembic's main function
from alembic.config import main
# Execute alembic command with sys.argv (e.g., 'upgrade', 'head')
main(argv=sys.argv[1:])
except Exception as e:
print(f"Error running migrations: {e}")
sys.exit(1)