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:
parent
84555ef4e8
commit
d024248fe9
34
README.md
34
README.md
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
Loading…
x
Reference in New Issue
Block a user