fix: resolve Docker volume permission denied on startup
Root cause: Docker named volumes are created as root, but the container ran as the unprivileged 'oxicloud' user (UID 1001). Services like thumbnail_service, image_transcode, and dedup_service call create_dir_all under /app/storage during initialization, which fails with 'Permission denied (os error 13)'. Changes: - Add entrypoint.sh that runs as root to chown /app/storage, then drops privileges via su-exec before executing the application - Update Dockerfile to install su-exec, copy entrypoint, and use ENTRYPOINT instead of USER+CMD - Downgrade id_mapping_service initial write failure from ERROR to WARN (empty in-memory map is perfectly valid, will persist on next save) - Improve panic message in main.rs to hint at Docker permission issue Fixes #<issue>
This commit is contained in:
+11
-6
@@ -37,8 +37,9 @@ LABEL org.opencontainers.image.title="OxiCloud" \
|
||||
org.opencontainers.image.licenses="MIT"
|
||||
|
||||
# Install only necessary runtime dependencies and update packages
|
||||
# su-exec is needed by the entrypoint to drop privileges after fixing volume permissions
|
||||
RUN apk --no-cache upgrade && \
|
||||
apk add --no-cache libgcc ca-certificates libpq tzdata
|
||||
apk add --no-cache libgcc ca-certificates libpq tzdata su-exec
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1001 -S oxicloud && \
|
||||
@@ -48,12 +49,16 @@ RUN addgroup -g 1001 -S oxicloud && \
|
||||
COPY --from=builder /app/target/release/oxicloud /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/oxicloud
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
# Copy static files and other resources needed at runtime
|
||||
COPY --chown=oxicloud:oxicloud static /app/static
|
||||
COPY --chown=oxicloud:oxicloud db /app/db
|
||||
|
||||
# Create storage directory with proper permissions
|
||||
RUN mkdir -p /app/storage && chown oxicloud:oxicloud /app/storage
|
||||
RUN mkdir -p /app/storage && chown -R oxicloud:oxicloud /app/storage
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
@@ -61,8 +66,8 @@ WORKDIR /app
|
||||
# Expose application port
|
||||
EXPOSE 8086
|
||||
|
||||
# Run as non-root user
|
||||
USER oxicloud
|
||||
|
||||
# Run the application
|
||||
# Entrypoint fixes volume permissions then drops to oxicloud user.
|
||||
# The container starts as root so it can chown mounted volumes,
|
||||
# then su-exec drops privileges before running the application.
|
||||
ENTRYPOINT ["entrypoint.sh"]
|
||||
CMD ["oxicloud"]
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Fix ownership of mounted volumes.
|
||||
# When Docker creates named volumes they are owned by root, but the
|
||||
# application runs as the unprivileged "oxicloud" user (UID 1001).
|
||||
# This script runs as root, fixes permissions, then drops privileges.
|
||||
|
||||
STORAGE_DIR="/app/storage"
|
||||
STATIC_DIR="/app/static"
|
||||
|
||||
# Ensure the storage directory exists and is writable by oxicloud
|
||||
if [ -d "$STORAGE_DIR" ]; then
|
||||
chown -R oxicloud:oxicloud "$STORAGE_DIR"
|
||||
fi
|
||||
|
||||
# Ensure static directory is readable
|
||||
if [ -d "$STATIC_DIR" ]; then
|
||||
chown -R oxicloud:oxicloud "$STATIC_DIR"
|
||||
fi
|
||||
|
||||
# Drop privileges and exec the main binary (or whatever was passed as CMD)
|
||||
exec su-exec oxicloud "$@"
|
||||
@@ -181,11 +181,11 @@ impl IdMappingService {
|
||||
}
|
||||
}
|
||||
|
||||
// Write empty map to file
|
||||
// Write empty map to file (best-effort: the in-memory map is valid even if disk write fails)
|
||||
match serde_json::to_string_pretty(&empty_map) {
|
||||
Ok(json) => {
|
||||
if let Err(e) = fs::write(map_path, json).await {
|
||||
tracing::error!("Failed to write initial empty ID map: {}", e);
|
||||
tracing::warn!("Could not write initial empty ID map (will retry on next save): {}", e);
|
||||
} else {
|
||||
tracing::info!("Created initial empty ID map at {}", map_path.display());
|
||||
}
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
);
|
||||
|
||||
let app_state = factory.build_app_state(db_pool).await
|
||||
.expect("Failed to build application state");
|
||||
.expect("Failed to build application state. If running in Docker, ensure the storage volume is writable by the oxicloud user (UID 1001)");
|
||||
|
||||
// Build application router
|
||||
let api_routes = create_api_routes(&app_state);
|
||||
|
||||
Reference in New Issue
Block a user