A modern Docker setup for hosting Python websites with Caddy web server and SSH access for Fabric deployments.
- Python 3.14.7: Supported Python version with common extensions.
- Caddy: Modern web server with automatic HTTPS.
- SSH Access: For automated deployments with Fabric.
- Automatic Site Discovery: Just add your site folder and it works.
- Development Domains: Test with .local domains that map to production folders.
- Automatic WWW Redirection: For second-level domains (e.g., example.com → www.example.com).
- Auto-HTTPS: Certificates are automatically generated on-demand.
- Environment Separation: Development and production environments managed through Docker Compose override.
- Optional PHP embed + phpy: Build PHP with
--enable-embedand install phpy so Python code running in this container can host and call PHP libraries directly (e.g.derafu/backbone-bridge-python). Disabled by default; see PHP embed + phpy (optional) below.
- Docker and Docker Compose installed on your system.
- SSH key for deployment access.
-
Clone this repository:
git clone https://github.com/derafu/docker-python3.14-caddy-server.git cd docker-python3.14-caddy-server -
Add your SSH public key to
config/ssh/authorized_keysfor admin, and default deployment, access:cat ~/.ssh/id_rsa.pub > config/ssh/authorized_keys
-
Build and start the container:
docker-compose up -d
The
-dparameter runs it in detached mode (background).
Check that the container is running:
docker-compose psView container logs:
docker-compose logs -fThe -f parameter allows you to follow logs in real-time.
-
Create the site directory structure:
mkdir -p sites/www.example.com/ cd sites/www.example.com/ -
Create project Django
python3 -m venv venv source venv/bin/activate pip install django django-admin startproject example .
-
Run a single site manually
/scripts/start_sites.sh www.example.com
Note: If no site is specified, the script will start all available sites under /var/www/sites.
-
Access the site at:
- Production mode: https://www.example.com (requires DNS configuration).
- Development mode: https://www.example.com.local:8443 (requires local hosts entry).
For local development, add to your /etc/hosts file:
127.0.0.1 www.example.com.local
This image can optionally build PHP from source with --enable-embed and install
phpy, so Python code can load and call a PHP
library directly in the same process (the Python-hosts-PHP direction; the
opposite direction, PHP-hosts-Python, is what docker-php8.5-caddy-server provides).
This is disabled by default: no regular PHP package (apt, homebrew, official
Docker images) ships with --enable-embed, so getting it requires compiling PHP
from source, which adds several minutes to the image build. Enable it explicitly
when you actually need it:
# In your .env file:
PHPY_ENABLED=true
PHPY_PHP_VERSION=8.5.3 # optional, defaults to 8.5.3
docker compose build
docker compose up -dWhen enabled, the embed-enabled PHP build lives at /opt/php (php, php-config,
phpize on PATH), and phpy is installed into the container's Python.
Verify it works with:
docker compose exec webserver python3 -c "import phpy; print(phpy)"Building with the default PHPY_ENABLED=false skips all of this and behaves
exactly like a normal Python + Caddy image.
docker-python3.14-caddy-server/
├── Dockerfile # Python + Caddy server base image definition (+ optional PHP/phpy)
├── docker-compose.yml # Production Docker services and volumes
├── .env # Environment variables for docker-compose
├── LICENSE # Project license
├── README.md # Main project documentation
Configuration
├── config/
│ ├── bash/
│ │ └── bashrc # Shell prompt / history tweaks for container user
│ ├── caddy/
│ │ └── Caddyfile # Caddy reverse proxy rules (HTTPS, domains, routing)
│ ├── cron/
│ │ └── logrotate # Cron job for rotating logs periodically
│ ├── logrotate/
│ │ ├── caddy # Logrotate rules for Caddy
│ │ └── gunicorn # Logrotate rules for Gunicorn
│ ├── ssh/
│ │ ├── authorized_keys # Public keys for SSH login (e.g., deploy access)
│ │ └── sshd_config # SSH server settings (OpenSSH)
│ └── supervisor/
│ └── supervisord.conf # Supervisor config to manage processes (Caddy, Gunicorn, etc.)
Development
├── sites/ # Django projects, one per domain
│ └── www.example.com/ # Project folder for www.example.com
Helper Scripts
├── scripts/
│ └── start_procfile_supervisord.sh # Auto-detect and launch Gunicorn/Celery for each site under /sites
Documentation
├── docs/
│ └── docker.md # Notes and recommendations for Docker usage
This project uses Docker Compose's override functionality to separate development and production configurations:
The base docker-compose.yml contains the minimal configuration needed for production deployment. It:
- Sets up required environment variables.
- Defines essential ports (HTTP, HTTPS, SSH).
- Doesn't mount external volumes.
The docker-compose.override.yml file adds development-specific settings:
- Adds additional development ports (e.g., management interface).
- Mounts local volumes for easy site development.
-
Development: Docker Compose automatically merges both files:
docker-compose up -d
-
Production: Use only the base configuration:
docker-compose -f docker-compose.yml up -d
Connect to the container via SSH:
ssh admin@localhost -p 2222Access the container shell:
docker exec -it derafu-sites-server-python-caddy bashRestart Caddy web server:
docker exec -it derafu-sites-server-python-caddy supervisorctl restart caddydocker-compose downRebuild for development:
docker-compose build --no-cache
docker-compose up -dRebuild for production:
docker-compose -f docker-compose.yml build --no-cache
docker-compose -f docker-compose.yml up -d-
Create the site directory structure:
mkdir -p sites/www.newsite.com/ cd sites/www.newsite.com/ -
Create project Django
python3 -m venv venv source venv/bin/activate pip install django django-admin startproject newsite .
NOTE: It's important that the requirements.txt file includes the gunicorn dependency (Example gunicorn==23.0.0)
-
No server restart required! Caddy automatically detects new sites.
-
For local development, add to your hosts file:
127.0.0.1 www.newsite.com.local
Customize behavior through environment variables:
| Variable | Description | Default |
|---|---|---|
SERVER_NAME |
Name for the docker container | derafu-sites-server |
CADDY_DEBUG |
Enable debug mode with debug |
(empty) |
CADDY_EMAIL |
Email for Let's Encrypt | admin@example.com |
CADDY_HTTPS_ISSUER |
TLS issuer (internal, acme) | internal |
CADDY_HTTPS_ALLOW_ANY_HOST |
Allow any host for TLS | false |
CADDY_LOG_SIZE |
Log file max size | 100mb |
CADDY_LOG_KEEP |
Number of log files to keep | 5 |
WWW_ROOT_PATH |
Web root path | /var/www/sites |
WWW_USER |
WWW and SSH user in the container | admin |
WWW_GROUP |
WWW group in the container | www-data |
HTTP_PORT |
HTTP port in host | 8080 |
HTTPS_PORT |
HTTPS port in host | 8443 |
SSH_PORT |
SSH port in host | 2222 |
PHPY_ENABLED |
Build PHP (--enable-embed) + phpy |
false |
PHPY_PHP_VERSION |
PHP version to build when PHPY_ENABLED=true |
8.5.3 |
The server handles domains in the following way:
-
Development domains: Any domain ending with
.local(e.g.,www.example.com.local)- Maps to the same directory as its production counterpart.
- Uses internal self-signed certificates.
-
Production domains:
- Redirects from non-www to www for second-level domains.
- Automatically obtains and manages Let's Encrypt certificates (issuer
acme).
If you're having issues with SSL certificates in development:
- Ensure your browser trusts self-signed certificates.
- Try using HTTP instead of HTTPS for local development.
If you encounter permission issues:
docker exec -it derafu-sites-server-python-caddy chown -R admin:www-data /var/www/sitesLogs are available in the container and can be accessed with:
docker exec -it derafu-sites-server-python-caddy cat /var/log/caddy/access.logFor advanced configurations, modify the Caddyfile at config/caddy/Caddyfile.
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This package is open-sourced software licensed under the MIT license.