build: dockerize app, migrate on boot

This commit is contained in:
2026-06-04 00:07:55 +03:00
parent 91932d38ce
commit f8c56623a4
5 changed files with 83 additions and 2 deletions

7
.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
**/target
**/node_modules
**/.svelte-kit
frontend/build
.git
**/.env
**/.env.*

3
.gitignore vendored
View File

@@ -18,5 +18,8 @@ node_modules/
# ─── env / secrets ─── # # ─── env / secrets ─── #
.env .env
.env.test
.env.local .env.local
.env.*.local .env.*.local
docs/

27
Dockerfile Normal file
View File

@@ -0,0 +1,27 @@
FROM node:22-alpine AS frontend-builder
WORKDIR /frontend
RUN npm install -g pnpm@10
COPY frontend/package.json frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY frontend/ ./
RUN pnpm build
FROM rust:1-bookworm AS backend-builder
WORKDIR /backend
ENV SQLX_OFFLINE=true
COPY backend/ ./
RUN cargo build --release
FROM debian:12-slim AS runtime
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /app/storage
COPY --from=backend-builder /backend/target/release/backend /app/backend
COPY --from=frontend-builder /frontend/build /app/static
ENV STORAGE_PATH=/app/storage
EXPOSE 3000
CMD ["/app/backend"]

View File

@@ -13,7 +13,11 @@ use axum::{
}; };
use sqlx::PgPool; use sqlx::PgPool;
use tower_cookies::CookieManagerLayer; use tower_cookies::CookieManagerLayer;
use tower_http::{cors::CorsLayer, services::ServeDir, trace::TraceLayer}; use tower_http::{
cors::CorsLayer,
services::{ServeDir, ServeFile},
trace::TraceLayer,
};
use tracing::info; use tracing::info;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
@@ -39,8 +43,10 @@ async fn main() -> Result<()> {
.init(); .init();
dotenvy::dotenv().ok(); dotenvy::dotenv().ok();
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = PgPool::connect(&database_url).await.unwrap(); let pool = PgPool::connect(&database_url).await.unwrap();
sqlx::migrate!().run(&pool).await?;
let file_repository = FileRepository::new(pool.clone())?; let file_repository = FileRepository::new(pool.clone())?;
let routes_file = routes_file(file_repository.clone()) let routes_file = routes_file(file_repository.clone())
.route_layer(middleware::from_fn(mw_require_auth)) .route_layer(middleware::from_fn(mw_require_auth))
@@ -67,7 +73,9 @@ async fn main() -> Result<()> {
.allow_credentials(true) .allow_credentials(true)
.allow_headers([header::CONTENT_TYPE]), .allow_headers([header::CONTENT_TYPE]),
) )
.fallback_service(ServeDir::new("./")); .fallback_service(
ServeDir::new("/app/static").fallback(ServeFile::new("/app/static/index.html")),
);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
info!("listening on {}", listener.local_addr().unwrap()); info!("listening on {}", listener.local_addr().unwrap());

36
compose.yml Normal file
View File

@@ -0,0 +1,36 @@
services:
db:
image: docker.io/library/postgres:16
restart: unless-stopped
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=pass
- POSTGRES_DB=loft
ports:
- "5432:5432"
volumes:
- loft_db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d loft"]
interval: 5s
timeout: 5s
retries: 5
app:
image: loft
restart: unless-stopped
build: .
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
environment:
- DATABASE_URL=postgres://postgres:pass@db:5432/loft
- STORAGE_PATH=/app/storage
volumes:
- loft_storage:/app/storage
volumes:
loft_db_data:
loft_storage: