1
0
Fork 0
mirror of https://github.com/seanmorley15/AdventureLog.git synced 2025-08-05 21:25:19 +02:00

feat(devsecops): optimized runtime containers

organized docker-compose files
updated pipelines to fix created date
using nonroot user in runtime environment
synced .env.example and docker compose file
normalized usage of PG variables
This commit is contained in:
Corbin Whitton 2025-06-22 20:52:37 -06:00
parent cadea118d3
commit 3c3288eab9
29 changed files with 784 additions and 412 deletions

View file

@ -1,47 +1,46 @@
# 🌐 Frontend # 🌐 Frontend
FRONTEND_PORT=8015 # Don't forget to update CSRF_TRUSTED_ORIGINS in backend and FRONTEND_URL in backend and ORIGIN in frontend if you make changes to this.
PUBLIC_SERVER_URL=http://server:8000 # PLEASE DON'T CHANGE :) - Should be the service name of the backend with port 8000, even if you change the port in the backend service. Only change if you are using a custom more complex setup. PUBLIC_SERVER_URL=http://server:8000 # PLEASE DON'T CHANGE :) - Should be the service name of the backend with port 8000, even if you change the port in the backend service. Only change if you are using a custom more complex setup.
ORIGIN=http://localhost:8015 ORIGIN=http://localhost:8015
BODY_SIZE_LIMIT=Infinity BODY_SIZE_LIMIT=Infinity
FRONTEND_PORT=8015
# 🐘 PostgreSQL Database # 🐘 PostgreSQL Database
PGHOST=db PGHOST=db # Supposed to match the host of the postgis database
POSTGRES_DB=database POSTGRES_DB=adventureloga
POSTGRES_USER=adventure POSTGRES_USER=adventurelog
POSTGRES_PASSWORD=changeme123 POSTGRES_PASSWORD=changeme123
# 🔒 Django Backend # 🔒 Django Backend
SECRET_KEY=changeme123 SECRET_KEY=changeme123 # Replace with the actual secret key
DJANGO_ADMIN_USERNAME=admin DJANGO_ADMIN_USERNAME=admin # Replace with the actual admin username
DJANGO_ADMIN_PASSWORD=admin DJANGO_ADMIN_PASSWORD=admin # Replace with the actual admin password
DJANGO_ADMIN_EMAIL=admin@example.com DJANGO_ADMIN_EMAIL=admin@example.com # Replace with the actual admin email
PUBLIC_URL=http://localhost:8016 # Match the outward port, used for the creation of image urls BACKEND_PORT=8016 # Don't forget to update CSRF_TRUSTED_ORIGINS and PUBLIC_URL in backend, and PUBLIC_SERVER_URL in frontend if you make changes to this.
CSRF_TRUSTED_ORIGINS=http://localhost:8016,http://localhost:8015 PUBLIC_URL=http://localhost:8016 # Replace with your domain where the backend is accessible publicly, used for the creation of image urls
DEBUG=False CSRF_TRUSTED_ORIGINS=http://localhost:8016,http://localhost:8015 # Replace with your domain, if applicable
FRONTEND_URL=http://localhost:8015 # Used for email generation. This should be the url of the frontend FRONTEND_URL=http://localhost:8015 # Replace with your domain where the frontend is accessible, used for email generation.
BACKEND_PORT=8016 # Disabling Registration: https://adventurelog.app/docs/configuration/disable_registration.html
# DISABLE_REGISTRATION=False
# DISABLE_REGISTRATION_MESSAGE='Registration is disabled for this instance of AdventureLog.'
# Optional: use Google Maps integration
# https://adventurelog.app/docs/configuration/google_maps_integration.html
# GOOGLE_MAPS_API_KEY=your_google_maps_api_key
# Optional: disable registration # Integrations
# https://adventurelog.app/docs/configuration/disable_registration.html ## Email: https://adventurelog.app/docs/configuration/email.html
DISABLE_REGISTRATION=False
# DISABLE_REGISTRATION_MESSAGE=Registration is disabled for this instance of AdventureLog.
# Optional: Use email
# https://adventurelog.app/docs/configuration/email.html
# EMAIL_BACKEND=email # EMAIL_BACKEND=email
# EMAIL_HOST=smtp.gmail.com # EMAIL_HOST=smtp.mail.com
# EMAIL_USE_TLS=True # EMAIL_USE_TLS=False
# EMAIL_PORT=587 # EMAIL_PORT=587
# EMAIL_USE_SSL=False # EMAIL_USE_SSL=True
# EMAIL_HOST_USER=user # EMAIL_HOST_USER=user
# EMAIL_HOST_PASSWORD=password # EMAIL_HOST_PASSWORD=password
# DEFAULT_FROM_EMAIL=user@example.com # DEFAULT_FROM_EMAIL=adventurelog@example.com
# Optional: Use Umami for analytics ## Google Maps: https://adventurelog.app/docs/configuration/google_maps_integration.html
# https://adventurelog.app/docs/configuration/analytics.html # GOOGLE_MAPS_API_KEY=your_google_maps_api_key
## Umami for analytics: https://adventurelog.app/docs/configuration/analytics.html
# PUBLIC_UMAMI_SRC=https://cloud.umami.is/script.js # If you are using the hosted version of Umami # PUBLIC_UMAMI_SRC=https://cloud.umami.is/script.js # If you are using the hosted version of Umami
# PUBLIC_UMAMI_WEBSITE_ID= # PUBLIC_UMAMI_WEBSITE_ID=
# Extra configuration
DEBUG=false # Use to enable debugging

View file

@ -6,8 +6,8 @@ services:
ports: ports:
- "127.0.0.1:5432:5432" - "127.0.0.1:5432:5432"
environment: environment:
POSTGRES_DB: database POSTGRES_DB: adventurelog
POSTGRES_USER: adventure POSTGRES_USER: adventurelog
POSTGRES_PASSWORD: changeme123 POSTGRES_PASSWORD: changeme123
volumes: volumes:
- postgres_data:/var/lib/postgresql/data/ - postgres_data:/var/lib/postgresql/data/

View file

@ -36,11 +36,42 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: "${{ github.repository_owner }}" OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "GIT_TAG=$(git describe --tags --abbrev=0 --exact-match 2>/dev/null || echo unknown)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build and push Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:beta -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:beta ./backend run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg GIT_TAG=${{ steps.vars.outputs.GIT_TAG }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/$REPO_OWNER/$IMAGE_NAME:beta \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:beta \
./backend
# Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for backend beta\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for backend beta\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

View file

@ -35,12 +35,43 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: '${{ github.repository_owner }}' OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "GIT_TAG=$(git describe --tags --abbrev=0 --exact-match 2>/dev/null || echo unknown)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build and push Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:latest -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest ./backend run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg GIT_TAG=${{ steps.vars.outputs.GIT_TAG }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/${{ steps.vars.outputs.REPO_OWNER }}/$IMAGE_NAME:latest \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest \
./backend
# Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for backend latest\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for backend latest\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

View file

@ -33,11 +33,43 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: "${{ github.repository_owner }}" OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "GIT_TAG=$(git describe --tags --abbrev=0 --exact-match 2>/dev/null || echo unknown)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build and push Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:${{ github.event.release.tag_name }} -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:${{ github.event.release.tag_name }} ./backend run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg GIT_TAG=${{ steps.vars.outputs.GIT_TAG }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/${{ steps.vars.outputs.REPO_OWNER }}/$IMAGE_NAME:${{ github.event.release.tag_name }} \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:${{ github.event.release.tag_name }} \
./backend
# # Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for backend release ${{ github.event.release.tag_name }}\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for backend release ${{ github.event.release.tag_name }}\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

View file

@ -43,10 +43,10 @@ jobs:
working-directory: backend/server working-directory: backend/server
env: env:
PGHOST: "127.0.0.1" PGHOST: "127.0.0.1"
PGDATABASE: "database" POSTGRES_DB: "adventurelog"
PGUSER: "adventure" POSTGRES_USER: "adventurelog"
PGPASSWORD: "changeme123" POSTGRES_PASSWORD: "changeme123"
SECRET_KEY: "changeme123" SECRET_KEY: "secretkey"
DJANGO_ADMIN_USERNAME: "admin" DJANGO_ADMIN_USERNAME: "admin"
DJANGO_ADMIN_PASSWORD: "admin" DJANGO_ADMIN_PASSWORD: "admin"
DJANGO_ADMIN_EMAIL: "admin@example.com" DJANGO_ADMIN_EMAIL: "admin@example.com"

View file

@ -36,11 +36,40 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: "${{ github.repository_owner }}" OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:beta -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:beta ./cdn run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/${{ steps.vars.outputs.REPO_OWNER }}/$IMAGE_NAME:beta \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:beta \
./cdn
# Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for CDN beta\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for CDN beta\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

View file

@ -36,11 +36,40 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: "${{ github.repository_owner }}" OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:latest -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest ./cdn run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/${{ steps.vars.outputs.REPO_OWNER }}/$IMAGE_NAME:latest \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest \
./cdn
# Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for CDN latest\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for CDN latest\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

View file

@ -33,11 +33,41 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: "${{ github.repository_owner }}" OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:${{ github.event.release.tag_name }} -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:${{ github.event.release.tag_name }} ./cdn run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/${{ steps.vars.outputs.REPO_OWNER }}/$IMAGE_NAME:${{ github.event.release.tag_name }} \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:${{ github.event.release.tag_name }} \
./cdn
# Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for CDN release ${{ github.event.release.tag_name }}\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for CDN release ${{ github.event.release.tag_name }}\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

View file

@ -36,11 +36,42 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: "${{ github.repository_owner }}" OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "GIT_TAG=$(git describe --tags --abbrev=0 --exact-match 2>/dev/null || echo unknown)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build and push Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:beta -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:beta ./frontend run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg GIT_TAG=${{ steps.vars.outputs.GIT_TAG }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/$REPO_OWNER/$IMAGE_NAME:beta \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:beta \
./frontend
# Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for frontend beta\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for frontend beta\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

View file

@ -35,12 +35,43 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: '${{ github.repository_owner }}' OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "GIT_TAG=$(git describe --tags --abbrev=0 --exact-match 2>/dev/null || echo unknown)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build and push Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:latest -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest ./frontend run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg GIT_TAG=${{ steps.vars.outputs.GIT_TAG }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/$REPO_OWNER/$IMAGE_NAME:latest \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:latest \
./frontend
# Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for frontend beta\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for frontend beta\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

View file

@ -33,11 +33,42 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: set lower case owner name - name: Set build metadata
run: | id: vars
echo "REPO_OWNER=${OWNER,,}" >>${GITHUB_ENV}
env: env:
OWNER: "${{ github.repository_owner }}" OWNER: '${{ github.repository_owner }}'
run: |
echo "GIT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
echo "GIT_TAG=$(git describe --tags --abbrev=0 --exact-match 2>/dev/null || echo unknown)" >> $GITHUB_OUTPUT
echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
echo "REPO_OWNER=${OWNER,,}" >> $GITHUB_OUTPUT
- name: Build Docker images - name: Build and push Docker images
run: docker buildx build --platform linux/amd64,linux/arm64 --push -t ghcr.io/$REPO_OWNER/$IMAGE_NAME:${{ github.event.release.tag_name }} -t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:${{ github.event.release.tag_name }} ./frontend run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
--build-arg GIT_SHA=${{ steps.vars.outputs.GIT_SHA }} \
--build-arg GIT_TAG=${{ steps.vars.outputs.GIT_TAG }} \
--build-arg BUILD_DATE=${{ steps.vars.outputs.BUILD_DATE }} \
-t ghcr.io/$REPO_OWNER/$IMAGE_NAME:${{ github.event.release.tag_name }} \
-t ${{ secrets.DOCKERHUB_USERNAME }}/$IMAGE_NAME:${{ github.event.release.tag_name }} \
./frontend
# Notify on success
# - name: Notify Discord on success
# if: success()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"✅ Build and push succeeded for frontend release ${{ github.event.release.tag_name }}\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}
# # Notify on failure
# - name: Notify Discord on failure
# if: failure()
# run: |
# curl -H "Content-Type: application/json" \
# -X POST \
# -d "{\"username\": \"CI Bot\", \"content\": \"❌ Build or push failed for frontend release ${{ github.event.release.tag_name }}\"}" \
# ${{ secrets.DISCORD_WEBHOOK_BUILD }}

7
backend/.dockerignore Normal file
View file

@ -0,0 +1,7 @@
.env.example
.devcontainer
.github
server/build_files.sh
.gitignore
Dockerfile
.dockerignore

View file

@ -1,7 +1,49 @@
# Use the official Python slim image as the base image # -------------------
FROM python:3.13-slim # Stage 1: Build Stage
# -------------------
FROM docker.io/python:3.13-alpine AS builder
# Build-time metadata
ARG GIT_SHA=unknown
ARG GIT_TAG=latest
ARG BUILD_DATE=unknown
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/install
# Install build dependencies for Alpine
RUN apk add --no-cache \
build-base \
libpq \
libpq-dev \
gdal \
gdal-dev \
geos \
geos-dev \
postgresql-dev \
musl-dev \
python3-dev \
linux-headers \
git
# Set working directory
WORKDIR /build
# Copy entire project into /tmp
COPY . /tmp/
# Install Python dependencies into /install
RUN pip install --upgrade pip && \
pip install --target=/install -r /tmp/server/requirements.txt && \
python3 /tmp/server/manage.py collectstatic --noinput --verbosity 2
# -------------------------
# Stage 2: Final Production
# -------------------------
FROM docker.io/python:3.13-alpine AS final
# Metadata labels for the AdventureLog image
LABEL maintainer="Sean Morley" \ LABEL maintainer="Sean Morley" \
version="v0.10.0" \ version="v0.10.0" \
description="AdventureLog — the ultimate self-hosted travel companion." \ description="AdventureLog — the ultimate self-hosted travel companion." \
@ -12,49 +54,51 @@ LABEL maintainer="Sean Morley" \
org.opencontainers.image.url="https://raw.githubusercontent.com/seanmorley15/AdventureLog/refs/heads/main/brand/banner.png" \ org.opencontainers.image.url="https://raw.githubusercontent.com/seanmorley15/AdventureLog/refs/heads/main/brand/banner.png" \
org.opencontainers.image.source="https://github.com/seanmorley15/AdventureLog" \ org.opencontainers.image.source="https://github.com/seanmorley15/AdventureLog" \
org.opencontainers.image.vendor="Sean Morley" \ org.opencontainers.image.vendor="Sean Morley" \
org.opencontainers.image.created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ org.opencontainers.image.version="${GIT_TAG}" \
org.opencontainers.image.revision="${GIT_SHA}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.licenses="GPL-3.0" org.opencontainers.image.licenses="GPL-3.0"
# Set environment variables ENV PYTHONDONTWRITEBYTECODE=1 \
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 \
ENV PYTHONUNBUFFERED=1 PYTHONPATH="/install:/code" \
PATH="/install/bin:${PATH}" \
GDAL_LIBRARY_PATH="/usr/lib/libgdal.so.36.3.10.3" \
GEOS_LIBRARY_PATH="/usr/lib/libgeos_c.so"
# Set the working directory
# Copy all from builder, organize in one RUN step
COPY --from=builder /install /install
COPY --from=builder /tmp /tmp
RUN apk add --no-cache \
nginx\
supervisor\
bash\
curl\
libpq\
gdal\
gdal-dev\
geos\
geos-dev\
postgresql-client && \
mv /tmp/server /code && \
mkdir -p /code/static /code/media && \
mv /tmp/nginx.conf /etc/nginx/nginx.conf && \
mkdir -p /etc/supervisor/conf.d/ && \
mv /tmp/supervisord.conf /etc/supervisor/conf.d/supervisord.conf && \
mv /tmp/entrypoint.sh /code/entrypoint.sh && \
chmod +x /code/entrypoint.sh && \
rm -rf /tmp && \
addgroup -g 349 adventurelog && \
adduser -D -u 349 -G adventurelog adventurelog && \
chown -R 349:349 /code
WORKDIR /code WORKDIR /code
# Install system dependencies (Nginx included)
RUN apt-get update \
&& apt-get install -y git postgresql-client gdal-bin libgdal-dev nginx supervisor \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY ./server/requirements.txt /code/
RUN pip install --upgrade pip \
&& pip install -r requirements.txt
# Create necessary directories
RUN mkdir -p /code/static /code/media
# RUN mkdir -p /code/staticfiles /code/media
# Copy the Django project code into the Docker image
COPY ./server /code/
# Copy Nginx configuration
COPY ./nginx.conf /etc/nginx/nginx.conf
# Copy Supervisor configuration
COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# Collect static files
RUN python3 manage.py collectstatic --noinput --verbosity 2
# Set the entrypoint script
COPY ./entrypoint.sh /code/entrypoint.sh
RUN chmod +x /code/entrypoint.sh
# Expose ports for NGINX and Gunicorn
EXPOSE 80 8000 EXPOSE 80 8000
# Command to start Supervisor (which starts Nginx and Gunicorn) CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
CMD ["supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=5 \
CMD curl --fail --silent --show-error http://localhost:80/ || exit 1

View file

@ -1,4 +1,4 @@
#!/bin/bash #!/usr/bin/env bash
# Function to check PostgreSQL availability # Function to check PostgreSQL availability
# Helper to get the first non-empty environment variable # Helper to get the first non-empty environment variable
@ -13,22 +13,27 @@ get_env() {
} }
check_postgres() { check_postgres() {
if ! command -v psql > /dev/null 2>&1; then
>&2 echo "psql command not found — PostgreSQL client is not installed"
return 1
fi
local db_host local db_host
local db_user local db_user
local db_name local db_name
local db_pass local db_pass
db_host=$(get_env PGHOST) db_host="$(get_env PGHOST)"
db_user=$(get_env PGUSER POSTGRES_USER) db_user="$(get_env PGUSER POSTGRES_USER)"
db_name=$(get_env PGDATABASE POSTGRES_DB) db_name="$(get_env PGDATABASE POSTGRES_DB)"
db_pass=$(get_env PGPASSWORD POSTGRES_PASSWORD) db_pass="$(get_env PGPASSWORD POSTGRES_PASSWORD)"
PGPASSWORD="$db_pass" psql -h "$db_host" -U "$db_user" -d "$db_name" -c '\q' >/dev/null 2>&1 PGPASSWORD="$db_pass" psql -h "$db_host" -U "$db_user" -d "$db_name" -c '\q' >/dev/null 2>&1
} }
# Wait for PostgreSQL to become available # Wait for PostgreSQL to become available
until check_postgres; do until check_postgres; do
>&2 echo "PostgreSQL is unavailable - sleeping" >&2 echo "PostgreSQL is unavailable - sleeping"
sleep 1 sleep 1
done done
@ -73,8 +78,6 @@ else:
EOF EOF
fi fi
# Sync the countries and world travel regions
# Sync the countries and world travel regions # Sync the countries and world travel regions
python manage.py download-countries python manage.py download-countries
if [ $? -eq 137 ]; then if [ $? -eq 137 ]; then

View file

@ -1,36 +0,0 @@
PGHOST=''
PGDATABASE=''
PGUSER=''
PGPASSWORD=''
SECRET_KEY='pleasechangethisbecauseifyoudontitwillbeverybadandyouwillgethackedinlessthanaminuteguaranteed'
PUBLIC_URL='http://127.0.0.1:8000'
DEBUG=True
FRONTEND_URL='http://localhost:3000'
EMAIL_BACKEND='console'
# EMAIL_BACKEND='email'
# EMAIL_HOST='smtp.gmail.com'
# EMAIL_USE_TLS=False
# EMAIL_PORT=587
# EMAIL_USE_SSL=True
# EMAIL_HOST_USER='user'
# EMAIL_HOST_PASSWORD='password'
# DEFAULT_FROM_EMAIL='user@example.com'
# GOOGLE_MAPS_API_KEY='key'
# ------------------- #
# For Developers to start a Demo Database
# docker run --name adventurelog-development -e POSTGRES_USER=admin -e POSTGRES_PASSWORD=admin -e POSTGRES_DB=adventurelog -p 5432:5432 -d postgis/postgis:15-3.3
# PGHOST='localhost'
# PGDATABASE='adventurelog'
# PGUSER='admin'
# PGPASSWORD='admin'
# ------------------- #

View file

@ -20,12 +20,19 @@ load_dotenv()
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# GDAL library version path
GDAL_LIBRARY_PATH = getenv('GDAL_LIBRARY_PATH', None)
# GEOS library version path
GEOS_LIBRARY_PATH = getenv('GEOS_LIBRARY_PATH', None)
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = getenv('SECRET_KEY') SECRET_KEY = getenv('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = getenv('DEBUG', 'true').lower() == 'true' DEBUG = getenv('DEBUG', 'true').lower() == 'true'

View file

@ -3,12 +3,14 @@ nodaemon=true
[program:nginx] [program:nginx]
command=/usr/sbin/nginx -g "daemon off;" command=/usr/sbin/nginx -g "daemon off;"
user=nginx
autorestart=true autorestart=true
stdout_logfile=/dev/stdout stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr stderr_logfile=/dev/stderr
[program:gunicorn] [program:gunicorn]
command=/code/entrypoint.sh command=/code/entrypoint.sh
user=adventurelog
autorestart=true autorestart=true
stdout_logfile=/dev/stdout stdout_logfile=/dev/stdout
stderr_logfile=/dev/stderr stderr_logfile=/dev/stderr

View file

@ -1,7 +1,7 @@
# This script will create a backup of the adventurelog_media volume and store it in the current directory as adventurelog-backup.tar.gz # This script will create a backup of the adventurelog_media volume and store it in the current directory as adventurelog-backup.tar.gz
docker run --rm \ docker run --rm \
-v adventurelog_adventurelog_media:/backup-volume \ -v adventurelog_media:/backup-volume \
-v "$(pwd)":/backup \ -v "$(pwd)":/backup \
busybox \ busybox \
tar -zcvf /backup/adventurelog-backup.tar.gz /backup-volume tar -zcvf /backup/adventurelog-backup.tar.gz /backup-volume

View file

@ -1,36 +1,46 @@
# Use an official Python image as a base # ---------------------
FROM python:3.11-slim # Stage 1: Data builder
# ---------------------
FROM docker.io/python:3.11-slim AS builder
# Set the working directory ARG GIT_SHA=unknown
ARG BUILD_DATE=unknown
# Set workdir and environment
WORKDIR /app WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# Install required Python packages # Install required Python packages
RUN pip install --no-cache-dir requests osm2geojson RUN pip install --no-cache-dir requests osm2geojson
# Copy the script into the container # Copy and run data generation script
COPY main.py /app/main.py COPY main.py /app/main.py
# Run the script to generate the data folder and GeoJSON files (this runs inside the container)
RUN python -u /app/main.py RUN python -u /app/main.py
# Install Nginx # ------------------------
RUN apt update && apt install -y nginx && rm -rf /var/lib/apt/lists/* # Stage 2: Final Nginx-only
# ------------------------
FROM docker.io/nginx:alpine
# Copy the entire generated data folder to the Nginx serving directory ARG GIT_SHA=unknown
RUN mkdir -p /var/www/html/data && cp -r /app/data/* /var/www/html/data/ ARG BUILD_DATE=unknown
# Copy Nginx configuration # Optional metadata
LABEL org.opencontainers.image.revision="${GIT_SHA}" \
org.opencontainers.image.created="${BUILD_DATE}"
# Create destination directory
RUN mkdir -p /var/www/html/data
# Copy generated data and static files
COPY --from=builder /app/data /var/www/html/data
COPY index.html /usr/share/nginx/html/index.html
COPY nginx.conf /etc/nginx/nginx.conf COPY nginx.conf /etc/nginx/nginx.conf
# Copy the index.html file to the Nginx serving directory # Add entrypoint
COPY index.html /usr/share/nginx/html/index.html
# Expose port 80 for Nginx
EXPOSE 80
# Copy the entrypoint script into the container
COPY entrypoint.sh /app/entrypoint.sh COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh
# Set the entrypoint script as the default command EXPOSE 80
ENTRYPOINT ["/app/entrypoint.sh"] ENTRYPOINT ["/app/entrypoint.sh"]

View file

@ -5,4 +5,4 @@ docker compose pull
echo "Stating containers" echo "Stating containers"
docker compose up -d docker compose up -d
echo "All set!" echo "All set!"
docker logs adventurelog-backend --follow docker compose logs --follow

View file

@ -1,78 +0,0 @@
version: "3.9"
services:
traefik:
image: traefik:v2.11
command:
- "--api.insecure=true" # Enable Traefik dashboard (remove in production)
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencrypt.acme.email=your-email@example.com" # Replace with your email
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "traefik-letsencrypt:/letsencrypt"
labels:
- "traefik.enable=true"
db:
image: postgis/postgis:15-3.3
restart: unless-stopped
environment:
POSTGRES_DB: database
POSTGRES_USER: adventure
POSTGRES_PASSWORD: your_postgres_password # Replace with the actual password
volumes:
- postgres-data:/var/lib/postgresql/data/
web:
image: ghcr.io/seanmorley15/adventurelog-frontend:latest
restart: unless-stopped
environment:
PUBLIC_SERVER_URL: "http://server:8000"
BODY_SIZE_LIMIT: "100000"
labels:
- "traefik.enable=true"
- "traefik.http.routers.adventurelogweb.entrypoints=websecure"
- "traefik.http.routers.adventurelogweb.rule=Host(`yourdomain.com`) && !(PathPrefix(`/media`) || PathPrefix(`/admin`) || PathPrefix(`/static`) || PathPrefix(`/accounts`))" # Replace with your domain
- "traefik.http.routers.adventurelogweb.tls=true"
- "traefik.http.routers.adventurelogweb.tls.certresolver=letsencrypt"
depends_on:
- server
server:
image: ghcr.io/seanmorley15/adventurelog-backend:latest
restart: unless-stopped
environment:
PGHOST: "db"
PGDATABASE: "database"
PGUSER: "adventure"
PGPASSWORD: your_postgres_password # Replace with the actual password
SECRET_KEY: your_secret_key # Replace with the actual secret key
DJANGO_ADMIN_USERNAME: "admin"
DJANGO_ADMIN_PASSWORD: your_admin_password # Replace with the actual admin password
DJANGO_ADMIN_EMAIL: "adventurelog-admin@yourdomain.com" # Replace with your email
PUBLIC_URL: "https://yourdomain.com" # Replace with your domain
CSRF_TRUSTED_ORIGINS: "https://yourdomain.com" # Replace with your domain
DEBUG: "false"
FRONTEND_URL: "https://yourdomain.com" # Replace with your domain
volumes:
- adventurelog-media:/code/media
labels:
- "traefik.enable=true"
- "traefik.http.routers.adventurelogserver.entrypoints=websecure"
- "traefik.http.routers.adventurelogserver.rule=Host(`yourdomain.com`) && (PathPrefix(`/media`) || PathPrefix(`/admin`) || PathPrefix(`/static`) || PathPrefix(`/accounts`))" # Replace with your domain
- "traefik.http.routers.adventurelogserver.tls=true"
- "traefik.http.routers.adventurelogserver.tls.certresolver=letsencrypt"
depends_on:
- db
volumes:
postgres-data:
adventurelog-media:
traefik-letsencrypt:

View file

@ -1,36 +1,128 @@
name: adventurelog
# Common DB variables used by both backend and db
x-env-common: &env-common
POSTGRES_DB: adventurelog # If you change this, it needs to also be changed in the backend
POSTGRES_USER: adventurelog # If you change this, it needs to also be changed in the backend
POSTGRES_PASSWORD: changeme123 # If you change this, it needs to also be changed in the backend
# Environment variables for the backend service
x-env-backend: &env-backend
# Database Configuration
PGHOST: db # Supposed to match the host of the postgis database
<<: *env-common
# AdventureLog base configuration
SECRET_KEY: changeme123 # Replace with the actual secret key
DJANGO_ADMIN_USERNAME: admin # Replace with the actual admin username
DJANGO_ADMIN_PASSWORD: admin # Replace with the actual admin password
DJANGO_ADMIN_EMAIL: admin@example.com # Replace with the actual admin email
BACKEND_PORT: 8016 # Don't forget to update CSRF_TRUSTED_ORIGINS and PUBLIC_URL in backend, and PUBLIC_SERVER_URL in frontend if you make changes to this.
PUBLIC_URL: http://localhost:8016 # Replace with your domain where the backend is accessible publicly, used for the creation of image URLs
CSRF_TRUSTED_ORIGINS: http://localhost:8016,http://localhost:8015 # Replace with your domain, if applicable
FRONTEND_URL: http://localhost:8015 # Replace with your domain where the frontend is accessible, used for email generation.
# Optional integrations (commented out by default)
# DISABLE_REGISTRATION: False
# DISABLE_REGISTRATION_MESSAGE: Registration is disabled for this instance of AdventureLog.
# EMAIL_BACKEND: email
# EMAIL_HOST: smtp.mail.com
# EMAIL_USE_TLS: False
# EMAIL_PORT: 587
# EMAIL_USE_SSL: True
# EMAIL_HOST_USER: user
# EMAIL_HOST_PASSWORD: password
# DEFAULT_FROM_EMAIL: ${COMPOSE_PROJECT_NAME@example.com
# GOOGLE_MAPS_API_KEY: your_google_maps_api_key
# PUBLIC_UMAMI_SRC: https://cloud.umami.is/script.js # If you are using the hosted version of Umami
# PUBLIC_UMAMI_WEBSITE_ID:
# Extra configuration
DEBUG: false # Use to enable debugging
# Environment variables for the frontend service
x-env-frontend: &env-frontend
FRONTEND_PORT: 8015 # Don't forget to update CSRF_TRUSTED_ORIGINS in backend and FRONTEND_URL in backend and ORIGIN in frontend if you make changes to this.
PUBLIC_SERVER_URL: http://backend:8000 # PLEASE DON'T CHANGE :) - Should be the service name of the backend with port 8000, even if you change the port in the backend service. Only change if you are using a custom more complex setup.
ORIGIN: http://localhost:8015
BODY_SIZE_LIMIT: Infinity
services: services:
web:
#build: ./frontend/
image: ghcr.io/seanmorley15/adventurelog-frontend:latest
container_name: adventurelog-frontend
restart: unless-stopped
env_file: .env
ports:
- "${FRONTEND_PORT:-8015}:3000"
depends_on:
- server
db: db:
image: postgis/postgis:16-3.5 image: docker.io/postgis/postgis:16-3.5
container_name: adventurelog-db
restart: unless-stopped restart: unless-stopped
env_file: .env #env_file: .env # Disabled for security reasons, as all containers have access to these env vars.
environment: # Anything defined here (including the env-backend) overrides what's in the env_file.
<<: *env-common
volumes: volumes:
- postgres_data:/var/lib/postgresql/data/ - data:/var/lib/postgresql/data/
networks:
- default
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
interval: 20s
timeout: 10s
retries: 5
start_period: 10s
server: frontend:
#build: ./backend/ #build: ./frontend/
image: ghcr.io/seanmorley15/adventurelog-backend:latest image: localhost/adventurelog-frontend:testing
container_name: adventurelog-backend
restart: unless-stopped restart: unless-stopped
env_file: .env # env_file: .env # Disabled for security reasons, as all containers have access to these env vars.
environment: # Anything defined here (including the env-backend) overrides what's in the env_file.
<<: *env-frontend
ports: ports:
- "${BACKEND_PORT:-8016}:80" - "8015:3000"
networks:
- default
depends_on: depends_on:
- db backend:
condition: service_healthy
healthcheck:
test: ["CMD", "/nodejs/bin/node", "-e", "require('net').connect(3000).on('connect', () => process.exit(0)).on('error', () => process.exit(1))"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
# labels:
# Traefik example: Refer to https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/ for help on deploying Traefik.
# - "traefik.enable=true"
# - "traefik.http.routers.adventurelogweb.entrypoints=websecure"
# - "traefik.http.routers.adventurelogweb.rule=Host(`yourdomain.com`) && !(PathPrefix(`/media`) || PathPrefix(`/admin`) || PathPrefix(`/static`) || PathPrefix(`/accounts`))" # Replace with your domain
# - "traefik.http.routers.adventurelogweb.tls=true"
# - "traefik.http.routers.adventurelogweb.tls.certresolver=letsencrypt"
backend:
#build: ./backend/
image: localhost/adventurelog-backend:testing
restart: unless-stopped
# env_file: .env # Disabled for security reasons, as all containers have access to these env vars.
environment: # Anything defined here (including the env-backend) overrides what's in the env_file.
<<: *env-backend
networks:
- default
ports:
- "8016:80"
depends_on:
db:
condition: service_healthy
volumes: volumes:
- adventurelog_media:/code/media/ - media:/code/media/
healthcheck:
test: ["CMD", "curl", "--fail", "--silent", "--show-error", "http://localhost:80/"]
interval: 30s
timeout: 10s
start_period: 20s
retries: 5
# labels:
# Traefik example: Refer to https://doc.traefik.io/traefik/user-guides/docker-compose/basic-example/ for help on deploying Traefik.
# - "traefik.enable=true"
# - "traefik.http.routers.adventurelogserver.entrypoints=websecure"
# - "traefik.http.routers.adventurelogserver.rule=Host(`yourdomain.com`) && (PathPrefix(`/media`) || PathPrefix(`/admin`) || PathPrefix(`/static`) || PathPrefix(`/accounts`))" # Replace with your domain
# - "traefik.http.routers.adventurelogserver.tls=true"
# - "traefik.http.routers.adventurelogserver.tls.certresolver=letsencrypt"
volumes: volumes:
postgres_data: data:
adventurelog_media: media:

View file

@ -45,8 +45,8 @@ The `.env` file contains all the configuration settings for your AdventureLog in
| Name | Required | Description | Default Value | | Name | Required | Description | Default Value |
| ------------------- | -------- | --------------------- | ------------- | | ------------------- | -------- | --------------------- | ------------- |
| `PGHOST` | Yes | Internal DB hostname. | `db` | | `PGHOST` | Yes | Internal DB hostname. | `db` |
| `POSTGRES_DB` | Yes | DB name. | `database` | | `POSTGRES_DB` | Yes | DB name. | `adventurelog` |
| `POSTGRES_USER` | Yes | DB user. | `adventure` | | `POSTGRES_USER` | Yes | DB user. | `adventurelog` |
| `POSTGRES_PASSWORD` | Yes | DB password. | `changeme123` | | `POSTGRES_PASSWORD` | Yes | DB password. | `changeme123` |
### 🔒 Backend (server) ### 🔒 Backend (server)

6
frontend/.dockerignore Normal file
View file

@ -0,0 +1,6 @@
.env.example
.prettierignore
.prettierrc
.gitignore
Dockerfile
.dockerignore

View file

@ -1,7 +0,0 @@
PUBLIC_SERVER_URL=http://127.0.0.1:8000
BODY_SIZE_LIMIT=Infinity
# OPTIONAL VARIABLES FOR UMAMI ANALYTICS
PUBLIC_UMAMI_SRC=
PUBLIC_UMAMI_WEBSITE_ID=

View file

@ -1,49 +1,65 @@
# Use this image as the platform to build the app # ----------------------
FROM node:22-alpine AS external-website # Stage 1: Build the app
# ----------------------
FROM docker.io/node:22-alpine AS builder
ARG GIT_SHA=unknown
ARG GIT_TAG=latest
ARG BUILD_DATE=unknown
# Set working directory
WORKDIR /app
COPY . ./
# Install pnpm globally
RUN npm install -g pnpm && \
pnpm install --frozen-lockfile && \
rm -f .env && \
pnpm run build && \
mv /app/package.json /app/build/package.json;\
ls -lah .; \
ls -lah ./build
# ----------------------------
# Stage 2: Install Production Dependencies
# ----------------------------
FROM docker.io/node:22-alpine AS deps
WORKDIR /app
COPY --from=builder /app/build/package.json ./
RUN npm install --omit=dev && \
addgroup -g 349 adventurelog && \
adduser -D -u 349 -G adventurelog adventurelog && \
chown -R 349:349 /app
# ----------------------------
# Stage 3: Distroless Runtime
# ----------------------------
FROM gcr.io/distroless/nodejs20
# Metadata labels for the AdventureLog image
LABEL maintainer="Sean Morley" \ LABEL maintainer="Sean Morley" \
version="v0.10.0" \
description="AdventureLog — the ultimate self-hosted travel companion." \
org.opencontainers.image.title="AdventureLog" \ org.opencontainers.image.title="AdventureLog" \
org.opencontainers.image.description="AdventureLog is a self-hosted travel companion that helps you plan, track, and share your adventures." \ org.opencontainers.image.description="AdventureLog is a self-hosted travel companion that helps you plan, track, and share your adventures." \
org.opencontainers.image.version="v0.10.0" \
org.opencontainers.image.authors="Sean Morley" \ org.opencontainers.image.authors="Sean Morley" \
org.opencontainers.image.url="https://raw.githubusercontent.com/seanmorley15/AdventureLog/refs/heads/main/brand/banner.png" \ org.opencontainers.image.url="https://raw.githubusercontent.com/seanmorley15/AdventureLog/refs/heads/main/brand/banner.png" \
org.opencontainers.image.source="https://github.com/seanmorley15/AdventureLog" \ org.opencontainers.image.source="https://github.com/seanmorley15/AdventureLog" \
org.opencontainers.image.vendor="Sean Morley" \ org.opencontainers.image.vendor="Sean Morley" \
org.opencontainers.image.created="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \ org.opencontainers.image.version="${GIT_TAG}" \
org.opencontainers.image.revision="${GIT_SHA}" \
org.opencontainers.image.created="${BUILD_DATE}" \
org.opencontainers.image.licenses="GPL-3.0" org.opencontainers.image.licenses="GPL-3.0"
# The WORKDIR instruction sets the working directory for everything that will happen next USER 349:349
WORKDIR /app WORKDIR /app
# Install pnpm globally first COPY --from=builder /etc/passwd /etc/passwd
RUN npm install -g pnpm COPY --from=builder /etc/group /etc/group
COPY --from=builder /app/build ./
COPY --from=deps /app/node_modules ./node_modules
# Copy package files first for better Docker layer caching CMD ["index.js"]
COPY package.json pnpm-lock.yaml* ./
# Clean install all node modules using pnpm with frozen lockfile
RUN pnpm install --frozen-lockfile
# Copy the rest of the application files
COPY . .
# Remove the development .env file if present
RUN rm -f .env
# Build SvelteKit app
RUN pnpm run build
# Make startup script executable
RUN chmod +x ./startup.sh
# Change to non-root user for security
USER node:node
# Expose the port that the app is listening on
EXPOSE 3000 EXPOSE 3000
# Run startup.sh instead of the default command HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD ["./startup.sh"] CMD /nodejs/bin/node -e "require('net').connect(3000).on('connect', () => process.exit(0)).on('error', () => process.exit(1))"

View file

@ -1,5 +0,0 @@
#!/bin/sh
echo "The origin to be set is: $ORIGIN"
# Start the application
ORIGIN=$ORIGIN exec node build

View file

@ -1,78 +1,63 @@
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: example-name name: adventurelog
labels: labels:
app: adventure app: adventurelog
spec: spec:
replicas: 1 replicas: 1
selector: selector:
matchLabels: matchLabels:
app: adventure app: adventurelog
template: template:
metadata: metadata:
name: adventure
labels: labels:
app: adventure app: adventurelog
spec: spec:
volumes: volumes:
- name: adventure-journal - name: media
persistentVolumeClaim: persistentVolumeClaim:
claimName: adventure-journal-pvc claimName: media-pvc
- name: adventure-journal-db - name: data
persistentVolumeClaim: persistentVolumeClaim:
claimName: adventure-journal-db-pvc claimName: data-pvc
containers: containers:
- name: adventure-frontend - name: frontend
image: ghcr.io/seanmorley15/adventurelog-frontend:latest image: ghcr.io/seanmorley15/adventurelog-frontend:latest
imagePullPolicy: IfNotPresent
ports: ports:
- containerPort: 3000 - containerPort: 3000
env: env:
- name: FRONTEND_PORT
value: "8015"
- name: PUBLIC_SERVER_URL - name: PUBLIC_SERVER_URL
value: "http://internally-and-externally.reachable.io:80" value: "http://backend:8000"
- name: ORIGIN - name: ORIGIN
value: "http://url-typed-into-browser.io:80" value: "http://localhost:8015"
- name: BODY_SIZE_LIMIT - name: BODY_SIZE_LIMIT
value: "Infinity" value: "Infinity"
readinessProbe:
exec:
command: ["/nodejs/bin/node", "-e", "require('net').connect(3000).on('connect', () => process.exit(0)).on('error', () => process.exit(1))"]
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
- name: adventure-db - name: backend
image: postgis/postgis:15-3.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5432
volumeMounts:
- name: adventure-journal-db
mountPath: /var/lib/postgresql/data
env:
- name: POSTGRES_DB
value: database
- name: PGDATA
value: /var/lib/postgresql/data/pgdata/subdir
- name: POSTGRES_USER
value: adventure
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: adventurelog-secret
key: adventure-postgres-password
- name: adventure-backend
image: ghcr.io/seanmorley15/adventurelog-backend:latest image: ghcr.io/seanmorley15/adventurelog-backend:latest
imagePullPolicy: IfNotPresent
ports: ports:
- containerPort: 80 - containerPort: 80
- containerPort: 8000 - containerPort: 8000
volumeMounts: volumeMounts:
- name: adventure-journal - name: media
mountPath: /code/media mountPath: /code/media
env: env:
- name: PGHOST - name: PGHOST
value: "adventure-db-svc" value: "db"
- name: PGDATABASE - name: POSTGRES_DB
value: "database" value: "adventurelog"
- name: PGUSER - name: POSTGRES_USER
value: "adventure" value: "adventurelog"
- name: PGPASSWORD - name: PGPASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
@ -82,58 +67,101 @@ spec:
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: adventurelog-secret name: adventurelog-secret
key: adventure-postgres-password key: secret-key
- name: PUBLIC_URL
value: "http://internally-and-externally.reachable.io:80" # Match the outward port, used for the creation of image urls
- name: FRONTEND_URL
value: "http://url-typed-into-browser.io:80"
- name: CSRF_TRUSTED_ORIGINS
value: "http://url-typed-into-browser.io:80, http://internally-and-externally.reachable.io:80"
- name: DJANGO_ADMIN_USERNAME - name: DJANGO_ADMIN_USERNAME
value: "admin" value: "admin"
- name: DJANGO_ADMIN_PASSWORD - name: DJANGO_ADMIN_PASSWORD
value: "admin" value: "admin"
- name: DJANGO_ADMIN_EMAIL - name: DJANGO_ADMIN_EMAIL
value: "admin@example.com" value: "admin@example.com"
- name: BACKEND_PORT
value: "8016"
- name: PUBLIC_URL
value: "http://localhost:8016"
- name: FRONTEND_URL
value: "http://localhost:8015"
- name: CSRF_TRUSTED_ORIGINS
value: "http://localhost:8016,http://localhost:8015"
- name: DEBUG - name: DEBUG
value: "True" value: "false"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 20
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 5
- name: db
image: postgis/postgis:16-3.5
ports:
- containerPort: 5432
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
env:
- name: POSTGRES_DB
value: "adventurelog"
- name: POSTGRES_USER
value: "adventurelog"
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: adventurelog-secret
key: adventure-postgres-password
readinessProbe:
exec:
command: ["pg_isready", "-U", "adventurelog"]
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 10
failureThreshold: 5
restartPolicy: Always restartPolicy: Always
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: adventure-db-svc name: backend
spec: spec:
selector: selector:
app: adventure app: adventurelog
ports: ports:
- name: db - name: http
protocol: TCP port: 80
port: 5432 targetPort: 80
targetPort: 5432 - name: backend
port: 8000
targetPort: 8000
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: server name: frontend
spec: spec:
selector: selector:
app: adventure app: adventurelog
ports: ports:
- name: http - name: http
protocol: TCP port: 3000
port: 80 targetPort: 3000
targetPort: 80 ---
- name: base apiVersion: v1
protocol: TCP kind: Service
port: 8000 metadata:
targetPort: 8000 name: db
spec:
selector:
app: adventurelog
ports:
- name: postgres
port: 5432
targetPort: 5432
--- ---
# If you aren't automatically provisioning PVCs (i.e. with Longhorn, you'll need to also create the PV's)
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: adventure-journal-pvc name: media-pvc
spec: spec:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
@ -144,10 +172,19 @@ spec:
apiVersion: v1 apiVersion: v1
kind: PersistentVolumeClaim kind: PersistentVolumeClaim
metadata: metadata:
name: adventure-journal-db-pvc name: data-pvc
spec: spec:
accessModes: accessModes:
- ReadWriteOnce - ReadWriteOnce
resources: resources:
requests: requests:
storage: 10Gi storage: 10Gi
---
apiVersion: v1
kind: Secret
metadata:
name: adventurelog-secret
type: Opaque
stringData:
adventure-postgres-password: changeme123
secret-key: changeme123