Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Build and Deploy

on:
workflow_dispatch:
push:
branches:
- 'main'
- 'staging'
tags:
- 'v*'
pull_request:
branches:
- 'main'
- 'staging'

jobs:
build:
runs-on: ubuntu-latest

permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to GitHub Container Registry
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin

- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ./docker/Dockerfile
target: 'prod'
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

deploy:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
needs: build
strategy:
matrix:
include:
- environment: staging
branch: staging
target_path: "~/staging.mapdb.cncnet.org"
compose_file: "docker-compose.prod.yml"
nginx_conf: "docker/nginx.prod.conf"
# disabled for now
# - environment: production
# branch: main
# target_path: "~/prod2.mapdb.cncnet.org"
# compose_file: "docker-compose.prod.yml"
# nginx_conf: "docker/nginx.prod.conf"

steps:
- name: "Exit if not matching branch"
if: github.ref != format('refs/heads/{0}', matrix.branch)
run: echo "Not target branch for this deployment. Skipping..." && exit 0

- name: Checkout repository
uses: actions/checkout@v4

- name: Copy docker-compose and nginx config over ssh
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "${{ matrix.compose_file }},${{ matrix.nginx_conf }}"
target: "${{ matrix.target_path }}"

- name: SSH into server and deploy
uses: appleboy/[email protected]
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ${{ matrix.target_path }}
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
docker compose -f ${{ matrix.compose_file }} pull
docker compose -f ${{ matrix.compose_file }} down
docker compose -f ${{ matrix.compose_file }} up -d
23 changes: 13 additions & 10 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,35 +10,38 @@ services:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
env_file:
- .env
ports:
- "127.0.0.1:${POSTGRES_PORT}:${POSTGRES_PORT}"
command: -p ${POSTGRES_PORT}
networks:
- mapdb-network

django:
container_name: mapdb-django
build:
context: .
dockerfile: docker/Dockerfile
target: prod
image: ghcr.io/cncnet/cncnet-map-api:${APP_TAG}
volumes:
- ${HOST_MEDIA_ROOT}:/data/cncnet_silo # django will save user-uploaded files here. MEDIA_ROOT
- ${HOST_STATIC_ROOT}:/data/cncnet_static # django will gather static files here. STATIC_ROOT
ports:
- "8000:8000"
env_file:
- .env
depends_on:
- db
networks:
- mapdb-network

nginx-server:
# nginx proxies requests to django via gunicorn.
container_name: mapdb-nginx
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./docker/nginx.prod.conf:/etc/nginx/nginx.conf:ro
- ${HOST_STATIC_ROOT}:/usr/share/nginx/html/static # website static assets.
- ${HOST_MEDIA_ROOT}:/usr/share/nginx/html/silo # website user-uploaded files.
ports:
- "80:80"
- "${EXPOSED_PORT}:80"
depends_on:
- django
networks:
- mapdb-network

networks:
mapdb-network:
driver: bridge
37 changes: 22 additions & 15 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,35 +1,42 @@
FROM python:3.12-bookworm AS base

# Base environment configuration
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Configurable user setup
ENV USER=cncnet
ENV UID=1000

WORKDIR /cncnet-map-api

# This is just here to crash the build if you don't make a .env file.
COPY .env /cncnet-map-api/
# Install system dependencies
RUN apt-get update && apt-get install -y liblzo2-dev libmagic1

# Copy files needed for build
# Create non-root user with configurable name and UID
RUN useradd -m -u ${UID} ${USER}

# Copy necessary files for the build
COPY requirements.txt /cncnet-map-api
COPY requirements-dev.txt /cncnet-map-api
COPY web_entry_point.sh /cncnet-map-api

# liblzo2 is a compression library used by westwood.
# libmagic1 is used for detecting file mime types by analyzing the file contents.
RUN apt-get update && apt-get install -y liblzo2-dev libmagic1
# Set permissions and make script executable
RUN chmod +x /cncnet-map-api/web_entry_point.sh && \
chown -R ${USER}:${USER} /cncnet-map-api

RUN pip install --upgrade pip
RUN chmod +x /cncnet-map-api/web_entry_point.sh

FROM base AS dev
# The cflags are needed to build the lzo library on Apple silicon.
RUN CFLAGS=-I$(brew --prefix)/include LDFLAGS=-L$(brew --prefix)/lib pip install -r ./requirements-dev.txt
ENTRYPOINT "/cncnet-map-api/web_entry_point.sh"
RUN pip install -r ./requirements-dev.txt
USER ${USER}
ENTRYPOINT ["/cncnet-map-api/web_entry_point.sh"]

FROM base AS prod
# The cflags are needed to build the lzo library on Apple silicon.
RUN CFLAGS=-I$(brew --prefix)/include LDFLAGS=-L$(brew --prefix)/lib pip install -r ./requirements.txt

COPY . /cncnet-map-api

ENTRYPOINT "/cncnet-map-api/web_entry_point.sh"
RUN pip install -r ./requirements.txt
USER ${USER}
ENTRYPOINT ["/cncnet-map-api/web_entry_point.sh"]

FROM base AS debugger
# Just build, but don't run anything. Your debugger will run pytest, manage.py, etc for you.
Expand Down
34 changes: 34 additions & 0 deletions docker/nginx.prod.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
user nginx;
worker_processes 4;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
server {
listen 80;

# Serve static files: js, static images, etc.
location /static/ {
alias /usr/share/nginx/html/static/; # The nginx container's mounted volume.
expires 30d;
add_header Cache-Control public;
}

# Serve user uploaded files
location /silo/ {
alias /usr/share/nginx/html/silo/; # The container's mounted volume.
}

# Proxy requests to the Django app running in gunicorn
location / {
proxy_pass http://django:8000; # The Django app is exposed on the `django` container on port 8000
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
2 changes: 1 addition & 1 deletion kirovy/settings/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = get_env_var("DEBUG", False, validation_callback=not_allowed_on_prod)

ALLOWED_HOSTS = []
ALLOWED_HOSTS = get_env_var("ALLOWED_HOSTS", "localhost,mapdb-nginx").split(",")

MAX_UPLOADED_FILE_SIZE_MAP = file_utils.ByteSized(mega=25)

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ gunicorn>=22.0.0,<23.0.0
django-filter==25.1
orjson>=3.10.15,<4.0
structlog>=25.1.0,<26.0.0
drf-spectacular[sidecar]
1 change: 1 addition & 0 deletions web_entry_point.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/bin/sh
# Don't run this file manually. It's the entry point for the dockerfile.
python manage.py collectstatic --noinput
python manage.py migrate
Expand Down