mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-17 14:17:34 -04:00
347 lines
16 KiB
Docker
347 lines
16 KiB
Docker
# Multi-stage build for AliasVault single container deployment
|
|
# This creates a single container with all services for NAS/home server deployments
|
|
|
|
# ============================================
|
|
# Stage 1: Build .NET applications
|
|
# ============================================
|
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS dotnet-builder
|
|
|
|
# Install Python (required for WASM compilation)
|
|
RUN apt-get update && apt-get install -y python3 python3-pip && rm -rf /var/lib/apt/lists/*
|
|
|
|
WORKDIR /src
|
|
|
|
# Copy all source files
|
|
COPY apps/server/ ./apps/server/
|
|
COPY shared/ ./shared/
|
|
|
|
# Install required .NET workloads
|
|
RUN dotnet workload install wasm-tools
|
|
|
|
# Build all .NET applications
|
|
WORKDIR /src/apps/server
|
|
|
|
# Build API
|
|
RUN dotnet publish AliasVault.Api/AliasVault.Api.csproj -c Release -o /app/api
|
|
|
|
# Build Client (requires wasm-tools workload)
|
|
RUN dotnet publish AliasVault.Client/AliasVault.Client.csproj -c Release -o /app/client
|
|
|
|
# Build Admin
|
|
RUN dotnet publish AliasVault.Admin/AliasVault.Admin.csproj -c Release -o /app/admin
|
|
|
|
# Build SMTP Service
|
|
RUN dotnet publish Services/AliasVault.SmtpService/AliasVault.SmtpService.csproj -c Release -o /app/smtp
|
|
|
|
# Build Task Runner
|
|
RUN dotnet publish Services/AliasVault.TaskRunner/AliasVault.TaskRunner.csproj -c Release -o /app/taskrunner
|
|
|
|
# ============================================
|
|
# Stage 2: Final runtime image with s6-overlay
|
|
# ============================================
|
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0
|
|
|
|
# Install required packages
|
|
RUN apt-get update && apt-get install -y \
|
|
nginx \
|
|
postgresql-15 \
|
|
postgresql-client-15 \
|
|
openssl \
|
|
curl \
|
|
xz-utils \
|
|
netcat-openbsd \
|
|
gettext-base \
|
|
&& rm -rf /var/lib/apt/lists/* \
|
|
&& useradd -r -s /bin/bash -d /var/lib/postgresql postgres 2>/dev/null || true
|
|
|
|
# Install s6-overlay v3
|
|
ARG S6_OVERLAY_VERSION=3.2.0.2
|
|
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
|
|
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp
|
|
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz \
|
|
&& tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz \
|
|
&& rm /tmp/s6-overlay-*.tar.xz
|
|
|
|
# Create necessary directories
|
|
RUN mkdir -p \
|
|
/app/api \
|
|
/app/client \
|
|
/app/admin \
|
|
/app/smtp \
|
|
/app/taskrunner \
|
|
/data/postgres \
|
|
/data/database \
|
|
/data/certificates/ssl \
|
|
/data/certificates/app \
|
|
/data/certificates/letsencrypt \
|
|
/data/logs \
|
|
/etc/nginx/ssl \
|
|
/var/run/postgresql \
|
|
/var/www/certbot
|
|
|
|
# Copy built applications from builder stage
|
|
COPY --from=dotnet-builder /app/api /app/api
|
|
COPY --from=dotnet-builder /app/client /app/client
|
|
COPY --from=dotnet-builder /app/admin /app/admin
|
|
COPY --from=dotnet-builder /app/smtp /app/smtp
|
|
COPY --from=dotnet-builder /app/taskrunner /app/taskrunner
|
|
|
|
# Copy client nginx configuration and ensure wwwroot is accessible
|
|
COPY apps/server/AliasVault.Client/nginx.conf /app/client/nginx.conf
|
|
|
|
# Copy nginx configuration for single container deployment
|
|
COPY apps/server/nginx.single.conf /etc/nginx/nginx.single.conf
|
|
|
|
# ============================================
|
|
# S6 Service Definitions
|
|
# ============================================
|
|
|
|
# Container initialization
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/init-container && \
|
|
echo '#!/bin/sh' > /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'echo "[init-container] Initializing AliasVault single container..." >&2' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'echo "[init-container] Creating data directories..." >&2' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'mkdir -p /data/database /data/logs /data/certificates /data/postgres' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo '' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'echo "[init-container] Creating symbolic links for persistent data..." >&2' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'ln -sf /data/database /database' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'ln -sf /data/logs /logs' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'ln -sf /data/certificates /certificates' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo '' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'echo "[init-container] Setting database permissions..." >&2' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'chown -R postgres:postgres /data/postgres 2>/dev/null || true' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'chmod 700 /data/postgres 2>/dev/null || true' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo '' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo 'echo "[init-container] Container initialization complete" >&2' >> /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/init-container/up && \
|
|
echo "oneshot" > /etc/s6-overlay/s6-rc.d/init-container/type
|
|
|
|
# PostgreSQL service
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/postgres && \
|
|
{ echo '#!/bin/sh'; \
|
|
echo ''; \
|
|
echo '# Set PostgreSQL paths'; \
|
|
echo 'export PATH="/usr/lib/postgresql/15/bin:$PATH"'; \
|
|
echo 'export PGDATA="/data/postgres"'; \
|
|
echo ''; \
|
|
echo '# Initialize PostgreSQL if needed'; \
|
|
echo 'if [ ! -d "$PGDATA/base" ]; then'; \
|
|
echo ' echo "Initializing PostgreSQL database..."'; \
|
|
echo ' mkdir -p "$PGDATA" /data/logs'; \
|
|
echo ' chown -R postgres:postgres "$PGDATA" /data/logs'; \
|
|
echo ' chmod 700 "$PGDATA"'; \
|
|
echo ' su - postgres -c "/usr/lib/postgresql/15/bin/initdb -D $PGDATA"'; \
|
|
echo ' '; \
|
|
echo ' # Configure PostgreSQL'; \
|
|
echo ' echo "host all all 127.0.0.1/32 md5" >> "$PGDATA/pg_hba.conf"'; \
|
|
echo ' echo "listen_addresses = '\''127.0.0.1'\''" >> "$PGDATA/postgresql.conf"'; \
|
|
echo ' '; \
|
|
echo ' # Start PostgreSQL temporarily to create database and user'; \
|
|
echo ' su - postgres -c "/usr/lib/postgresql/15/bin/pg_ctl -D $PGDATA -l /data/logs/postgres.log start"'; \
|
|
echo ' sleep 5'; \
|
|
echo ' '; \
|
|
echo ' # Create database and user'; \
|
|
echo ' su - postgres -c "/usr/lib/postgresql/15/bin/psql -c \\"CREATE USER aliasvault WITH PASSWORD '\''${POSTGRES_PASSWORD:-defaultpassword}'\''\\""'; \
|
|
echo ' su - postgres -c "/usr/lib/postgresql/15/bin/psql -c \\"CREATE DATABASE aliasvault OWNER aliasvault;\\""'; \
|
|
echo ' su - postgres -c "/usr/lib/postgresql/15/bin/psql -c \\"GRANT ALL PRIVILEGES ON DATABASE aliasvault TO aliasvault;\\""'; \
|
|
echo ' '; \
|
|
echo ' # Stop PostgreSQL'; \
|
|
echo ' su - postgres -c "/usr/lib/postgresql/15/bin/pg_ctl -D $PGDATA stop"'; \
|
|
echo ' sleep 2'; \
|
|
echo 'fi'; \
|
|
echo ''; \
|
|
echo '# Run PostgreSQL'; \
|
|
echo 'exec s6-setuidgid postgres /usr/lib/postgresql/15/bin/postgres -D "$PGDATA"'; \
|
|
} > /etc/s6-overlay/s6-rc.d/postgres/run && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/postgres/run && \
|
|
echo "longrun" > /etc/s6-overlay/s6-rc.d/postgres/type && \
|
|
mkdir -p /etc/s6-overlay/s6-rc.d/postgres/dependencies.d && \
|
|
touch /etc/s6-overlay/s6-rc.d/postgres/dependencies.d/init-container
|
|
|
|
# Wait for PostgreSQL to be ready
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/postgres-ready && \
|
|
{ echo '#!/bin/sh'; \
|
|
echo 'echo "Waiting for PostgreSQL to be ready..."'; \
|
|
echo 'for i in {1..30}; do'; \
|
|
echo ' if su - postgres -c "/usr/lib/postgresql/15/bin/pg_isready -h localhost" > /dev/null 2>&1; then'; \
|
|
echo ' echo "PostgreSQL is ready"'; \
|
|
echo ' exit 0'; \
|
|
echo ' fi'; \
|
|
echo ' sleep 1'; \
|
|
echo 'done'; \
|
|
echo 'echo "PostgreSQL failed to start"'; \
|
|
echo 'exit 1'; \
|
|
} > /etc/s6-overlay/s6-rc.d/postgres-ready/up && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/postgres-ready/up && \
|
|
echo "oneshot" > /etc/s6-overlay/s6-rc.d/postgres-ready/type && \
|
|
mkdir -p /etc/s6-overlay/s6-rc.d/postgres-ready/dependencies.d && \
|
|
touch /etc/s6-overlay/s6-rc.d/postgres-ready/dependencies.d/postgres
|
|
|
|
# API service
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/api && \
|
|
{ echo '#!/command/with-contenv bash'; \
|
|
echo 'cd /app/api'; \
|
|
echo 'export ConnectionStrings__AliasServerDbContext="Host=localhost;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD:-defaultpassword}"'; \
|
|
echo 'export ASPNETCORE_URLS="http://0.0.0.0:3001"'; \
|
|
echo 'exec dotnet AliasVault.Api.dll'; \
|
|
} > /etc/s6-overlay/s6-rc.d/api/run && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/api/run && \
|
|
echo "longrun" > /etc/s6-overlay/s6-rc.d/api/type && \
|
|
mkdir -p /etc/s6-overlay/s6-rc.d/api/dependencies.d && \
|
|
touch /etc/s6-overlay/s6-rc.d/api/dependencies.d/postgres-ready
|
|
|
|
# Client service (nginx for WASM app) - using echo approach
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/client && \
|
|
{ echo '#!/command/with-contenv bash'; \
|
|
echo '# Client service entrypoint'; \
|
|
echo 'DEFAULT_PRIVATE_EMAIL_DOMAINS="localmail.tld"'; \
|
|
echo 'DEFAULT_SUPPORT_EMAIL=""'; \
|
|
echo 'PRIVATE_EMAIL_DOMAINS=${PRIVATE_EMAIL_DOMAINS:-$DEFAULT_PRIVATE_EMAIL_DOMAINS}'; \
|
|
echo 'SUPPORT_EMAIL=${SUPPORT_EMAIL:-$DEFAULT_SUPPORT_EMAIL}'; \
|
|
echo ''; \
|
|
echo 'mkdir -p /etc/nginx/ssl'; \
|
|
echo ''; \
|
|
echo 'if [ ! -f /etc/nginx/ssl/nginx.crt ] || [ ! -f /etc/nginx/ssl/nginx.key ]; then'; \
|
|
echo ' echo "Generating SSL certificate..."'; \
|
|
echo ' openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/ssl/nginx.key -out /etc/nginx/ssl/nginx.crt -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"'; \
|
|
echo ' chmod 644 /etc/nginx/ssl/nginx.crt'; \
|
|
echo ' chmod 600 /etc/nginx/ssl/nginx.key'; \
|
|
echo 'fi'; \
|
|
echo ''; \
|
|
echo '# Create simple JSON with environment variables'; \
|
|
echo 'cat > /app/client/wwwroot/appsettings.json << EOF'; \
|
|
echo '{'; \
|
|
echo ' "PrivateEmailDomains": ["$PRIVATE_EMAIL_DOMAINS"],'; \
|
|
echo ' "SupportEmail": "$SUPPORT_EMAIL",'; \
|
|
echo ' "PublicRegistrationEnabled": "$PUBLIC_REGISTRATION_ENABLED"'; \
|
|
echo '}'; \
|
|
echo 'EOF'; \
|
|
echo ''; \
|
|
echo 'sed -i "s|/usr/share/nginx/html|/app/client/wwwroot|g" /app/client/nginx.conf'; \
|
|
echo ''; \
|
|
echo 'exec nginx -c /app/client/nginx.conf -g "daemon off;"'; \
|
|
} > /etc/s6-overlay/s6-rc.d/client/run && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/client/run && \
|
|
echo "longrun" > /etc/s6-overlay/s6-rc.d/client/type
|
|
|
|
# Admin service
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/admin && \
|
|
{ echo '#!/command/with-contenv bash'; \
|
|
echo 'cd /app/admin'; \
|
|
echo 'export ConnectionStrings__AliasServerDbContext="Host=localhost;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD:-defaultpassword}"'; \
|
|
echo 'export ASPNETCORE_URLS="http://0.0.0.0:3002"'; \
|
|
echo 'exec dotnet AliasVault.Admin.dll'; \
|
|
} > /etc/s6-overlay/s6-rc.d/admin/run && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/admin/run && \
|
|
echo "longrun" > /etc/s6-overlay/s6-rc.d/admin/type && \
|
|
mkdir -p /etc/s6-overlay/s6-rc.d/admin/dependencies.d && \
|
|
touch /etc/s6-overlay/s6-rc.d/admin/dependencies.d/postgres-ready
|
|
|
|
# SMTP service
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/smtp && \
|
|
{ echo '#!/command/with-contenv bash'; \
|
|
echo 'cd /app/smtp'; \
|
|
echo 'export ConnectionStrings__AliasServerDbContext="Host=localhost;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD:-defaultpassword}"'; \
|
|
echo 'exec dotnet AliasVault.SmtpService.dll'; \
|
|
} > /etc/s6-overlay/s6-rc.d/smtp/run && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/smtp/run && \
|
|
echo "longrun" > /etc/s6-overlay/s6-rc.d/smtp/type && \
|
|
mkdir -p /etc/s6-overlay/s6-rc.d/smtp/dependencies.d && \
|
|
touch /etc/s6-overlay/s6-rc.d/smtp/dependencies.d/postgres-ready
|
|
|
|
# Task Runner service
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/taskrunner && \
|
|
{ echo '#!/command/with-contenv bash'; \
|
|
echo 'cd /app/taskrunner'; \
|
|
echo 'export ConnectionStrings__AliasServerDbContext="Host=localhost;Database=aliasvault;Username=aliasvault;Password=${POSTGRES_PASSWORD:-defaultpassword}"'; \
|
|
echo 'exec dotnet AliasVault.TaskRunner.dll'; \
|
|
} > /etc/s6-overlay/s6-rc.d/taskrunner/run && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/taskrunner/run && \
|
|
echo "longrun" > /etc/s6-overlay/s6-rc.d/taskrunner/type && \
|
|
mkdir -p /etc/s6-overlay/s6-rc.d/taskrunner/dependencies.d && \
|
|
touch /etc/s6-overlay/s6-rc.d/taskrunner/dependencies.d/postgres-ready
|
|
|
|
# Nginx service
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/nginx && \
|
|
{ echo '#!/command/with-contenv bash'; \
|
|
echo '# Generate SSL certificate if not exists'; \
|
|
echo 'if [ ! -f /data/certificates/ssl/cert.pem ]; then'; \
|
|
echo ' echo "Generating self-signed SSL certificate..."'; \
|
|
echo ' mkdir -p /data/certificates/ssl'; \
|
|
echo ' openssl req -x509 -nodes -days 365 -newkey rsa:2048 \\'; \
|
|
echo ' -keyout /data/certificates/ssl/key.pem \\'; \
|
|
echo ' -out /data/certificates/ssl/cert.pem \\'; \
|
|
echo ' -subj "/C=US/ST=State/L=City/O=Organization/CN=${HOSTNAME:-localhost}"'; \
|
|
echo 'fi'; \
|
|
echo ''; \
|
|
echo '# Copy certificates to nginx directory'; \
|
|
echo 'cp /data/certificates/ssl/* /etc/nginx/ssl/ 2>/dev/null || true'; \
|
|
echo ''; \
|
|
echo '# Create SSL configuration file'; \
|
|
echo 'cat > /etc/nginx/ssl.conf << "SSLEOF"'; \
|
|
echo 'ssl_certificate /etc/nginx/ssl/cert.pem;'; \
|
|
echo 'ssl_certificate_key /etc/nginx/ssl/key.pem;'; \
|
|
echo 'ssl_session_timeout 1d;'; \
|
|
echo 'ssl_session_cache shared:MozTLS:10m;'; \
|
|
echo 'ssl_session_tickets off;'; \
|
|
echo 'ssl_protocols TLSv1.2 TLSv1.3;'; \
|
|
echo 'ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;'; \
|
|
echo 'ssl_prefer_server_ciphers off;'; \
|
|
echo 'SSLEOF'; \
|
|
echo ''; \
|
|
echo '# Use single container nginx configuration'; \
|
|
echo 'cp /etc/nginx/nginx.single.conf /etc/nginx/nginx.conf'; \
|
|
echo ''; \
|
|
echo '# Wait for all services to be ready'; \
|
|
echo 'echo "Waiting for services to be ready..."'; \
|
|
echo 'for i in {1..60}; do'; \
|
|
echo ' if nc -z localhost 3000 && nc -z localhost 3001 && nc -z localhost 3002; then'; \
|
|
echo ' echo "All services ready, starting nginx..."'; \
|
|
echo ' break'; \
|
|
echo ' fi'; \
|
|
echo ' if [ $i -eq 60 ]; then'; \
|
|
echo ' echo "Timeout waiting for services"'; \
|
|
echo ' exit 1'; \
|
|
echo ' fi'; \
|
|
echo ' sleep 1'; \
|
|
echo 'done'; \
|
|
echo ''; \
|
|
echo 'exec nginx -g "daemon off;"'; \
|
|
} > /etc/s6-overlay/s6-rc.d/nginx/run && \
|
|
chmod +x /etc/s6-overlay/s6-rc.d/nginx/run && \
|
|
echo "longrun" > /etc/s6-overlay/s6-rc.d/nginx/type && \
|
|
mkdir -p /etc/s6-overlay/s6-rc.d/nginx/dependencies.d && \
|
|
touch /etc/s6-overlay/s6-rc.d/nginx/dependencies.d/api && \
|
|
touch /etc/s6-overlay/s6-rc.d/nginx/dependencies.d/client && \
|
|
touch /etc/s6-overlay/s6-rc.d/nginx/dependencies.d/admin
|
|
|
|
# Enable all services in the default bundle
|
|
RUN mkdir -p /etc/s6-overlay/s6-rc.d/user/contents.d && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/init-container && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/postgres && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/postgres-ready && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/api && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/client && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/admin && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/smtp && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/taskrunner && \
|
|
touch /etc/s6-overlay/s6-rc.d/user/contents.d/nginx
|
|
|
|
# Set environment variables for s6-overlay
|
|
ENV S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
|
|
S6_VERBOSITY=1 \
|
|
S6_BEHAVIOUR_IF_STAGE2_FAILS=2 \
|
|
S6_KILL_GRACETIME=30000
|
|
|
|
# Expose ports
|
|
EXPOSE 80 443 25 587
|
|
|
|
# Volume for persistent data
|
|
VOLUME ["/data"]
|
|
|
|
# Health check
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \
|
|
CMD curl -f -k https://localhost/api || exit 1
|
|
|
|
# Set s6-overlay as entrypoint
|
|
ENTRYPOINT ["/init"] |