diff --git a/cookiecutter.json b/cookiecutter.json index 057c868..3a6c8a7 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -18,5 +18,6 @@ "python_version": "3.11", "add_script": "y", "year": "2024", - "add_pypi_release_ci": "y" + "add_pypi_release_ci": "y", + "add_docker_image": "y" } diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index f4162bb..53950a4 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -5,6 +5,7 @@ create_main_file = '{{cookiecutter.add_script}}' == 'y' create_pypi_release_ci = '{{cookiecutter.add_pypi_release_ci}}' == 'y' +create_dockerfile = '{{cookiecutter.add_docker_image}}' == 'y' def remove(filepath): if Path.is_file(filepath): @@ -17,3 +18,7 @@ def remove(filepath): if not create_pypi_release_ci: main_file_path = path_to_workflows / 'release_pypi.yaml' remove(main_file_path) + +if not create_dockerfile: + dockerfile_path = Path.cwd() / 'deployment' / 'images' / 'Dockerfile' + remove(dockerfile_path) \ No newline at end of file diff --git a/{{ cookiecutter.project_name_dashed }}/.dockerignore b/{{ cookiecutter.project_name_dashed }}/.dockerignore new file mode 100644 index 0000000..cbb259f --- /dev/null +++ b/{{ cookiecutter.project_name_dashed }}/.dockerignore @@ -0,0 +1,39 @@ +# Ignore cache +__pycache__/ +*.pyc +*.pyo +*.pyd +.ruff_cache/ + +# Ignore virtual environments +.venv/ + +# Ignore version control files and directories +.git/ +.gitignore + +# Ignore CI/CD and deployment-related files +.github/ +.dockerignore +.cruft.json +.pre-commit-config.yaml +.safety-policy.yml +.readthedocs.yaml +.yamllint.yaml +.tool-versions + +# Ignore markdown files (e.g., documentation) +*.md + +# Ignore environment files (for local development) +.env + +# Ignore deployment files +deployment/ + +# Ignore documentation and images +docs/ +images/ + +# Ignore test files and directories +tests/ diff --git a/{{ cookiecutter.project_name_dashed }}/.github/workflows/release_docker.yaml b/{{ cookiecutter.project_name_dashed }}/.github/workflows/release_docker.yaml new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.project_name_dashed }}/Makefile b/{{ cookiecutter.project_name_dashed }}/Makefile index 014cbce..a036c2b 100644 --- a/{{ cookiecutter.project_name_dashed }}/Makefile +++ b/{{ cookiecutter.project_name_dashed }}/Makefile @@ -19,7 +19,12 @@ help: @echo " \033[1m\033[35mprecommit-check\033[0m \033[37m(pc)\033[0m: \033[36mRun all pre-commit checks.\033[0m" @echo " \033[1m\033[35msecurity\033[0m \033[37m(s)\033[0m: \033[36mRun security scans.\033[0m" @echo " \033[1m\033[35mtype-check\033[0m \033[37m(tc)\033[0m: \033[36mPerform type checking.\033[0m\n" - +{% if cookiecutter.add_docker_image == "y" %} + @echo "Deployment --------------------------------------------------------------------" + @echo " \033[1m\033[35mbuild-docker-image\033[0m \033[37m(bdi)\033[0m: \033[36mBuild the Docker image.\033[0m" + @echo " \033[1m\033[35mclean-docker-image\033[0m \033[37m(cdi)\033[0m: \033[36mRemove the Docker image.\033[0m" + @echo " \033[1m\033[35mrun-docker-image\033[0m \033[37m(rdi)\033[0m: \033[36mRun the Docker container.\033[\n" +{% endif %} @echo "Documentation -----------------------------------------------------------------" @echo " \033[1m\033[35mdocs\033[0m \033[37m(d)\033[0m: \033[36mGenerate project documentation.\033[0m\n" @@ -38,7 +43,25 @@ help: # NOTE: Keep all the targets in alphabetical order for better readability. # NOTE: Do not modify the autogenerated targets, unless necessary, write custom # targets in the custom section below.. - +{% if cookiecutter.add_docker_image == "y" %} +.PHONY: build-docker-image +build-docker-image: + @echo "\nBuilding Docker image +++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n" + @docker build -t {{ cookiecutter.github_username }}/{{ cookiecutter.project_name_dashed }}:latest -f deployment/images/Dockerfile . + @echo "\nDocker image built successfully: {{ cookiecutter.github_username }}/{{ cookiecutter.project_name_dashed }}:latest\n" + +.PHONY: bdi +bdi: build-docker-image + +.PHONY: clean-docker-image +clean-docker-image: + @echo "\nRemoving Docker image +++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n" + @docker rmi {{ cookiecutter.github_username }}/{{ cookiecutter.project_name_dashed }}:latest || echo "Docker image not found." + @echo "\nDocker image removed successfully (if it existed).\n" + +.PHONY: cdi +cdi: clean-docker-image +{% endif %} .PHONY: clean-venv clean-venv: @echo "\nRemoving the virtual environment ++++++++++++++++++++++++++++++++++++++++++++++\n" @@ -85,7 +108,16 @@ precommit-check: .PHONY: pc pc: precommit-check - +{% if cookiecutter.add_docker_image == "y" %} +.PHONY: run-docker-image +run-docker-image: + @echo "\nRunning Docker container +++++++++++++++++++++++++++++++++++++++++++++++++++++++\n" + @docker run --rm {{ cookiecutter.github_username }}/{{ cookiecutter.project_name_dashed }}:latest + @echo "\nDocker container executed successfully.\n" + +.PHONY: rdi +rdi: run-docker-image +{% endif %} .PHONY: security security: @echo "\nRunning security scans using bandit and safety ++++++++++++++++++++++++++++++++\n" diff --git a/{{ cookiecutter.project_name_dashed }}/deployment/images/Dockerfile b/{{ cookiecutter.project_name_dashed }}/deployment/images/Dockerfile new file mode 100644 index 0000000..89737f1 --- /dev/null +++ b/{{ cookiecutter.project_name_dashed }}/deployment/images/Dockerfile @@ -0,0 +1,76 @@ +################################################### +# Stage 1: Get poetry # +################################################### +FROM python:{{ cookiecutter.python_version }}-slim AS poetry + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ +PYTHONUNBUFFERED=1 + +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ +build-essential \ +curl \ +&& rm -rf /var/lib/apt/lists/* + +# Install Poetry +RUN curl -sSL https://install.python-poetry.org | python3 - && \ +ln -s /root/.local/bin/poetry /usr/local/bin/poetry + +################################################### +# Stage 2: Get deps # +################################################### +FROM poetry AS builder + +# Set work directory +WORKDIR /app + +# Copy poetry configuration +COPY pyproject.toml poetry.lock* /app/ + +# Install dependencies +RUN poetry config virtualenvs.create false \ + && poetry install --no-interaction --no-ansi --only main + +# Copy the rest of the application code +COPY . /app/ + +################################################### +# Stage 3: Create user and run # +################################################### +FROM python:{{ cookiecutter.python_version }}-slim + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +# Set work directory +WORKDIR /app + +# Copy dependencies from builder +COPY --from=builder /app /app + +# Create and switch to a non-root user +RUN addgroup --system app && adduser --system --ingroup app app +RUN chown -R app:app /app +USER app + +# Set labels for the Docker image +LABEL org.opencontainers.image.title="{{ cookiecutter.project_name }}" +LABEL org.opencontainers.image.description="{{ cookiecutter.short_description }}" +LABEL org.opencontainers.image.version="0.1.0" +LABEL org.opencontainers.image.authors="{{ cookiecutter.author_name }} <{{ cookiecutter.author_email }}>" +LABEL org.opencontainers.image.licenses="Apache-2.0" +LABEL org.opencontainers.image.source="https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_name_dashed }}" +LABEL org.opencontainers.image.url="https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_name_dashed }}" +LABEL org.opencontainers.image.documentation="https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_name_dashed }}/blob/main/README.md" +LABEL org.opencontainers.image.vendor="{{ cookiecutter.github_username }}" + +# Expose port (if your application uses a specific port, adjust accordingly) +# EXPOSE 8000 + +# Define the entry point to the console script +ENTRYPOINT ["python" , "{{ cookiecutter.project_name_underscored }}/main.py"] + +# Optional: Specify default command arguments +# CMD []