Merge branch 'develop'
This commit is contained in:
120
DEPLOYMENT.md
120
DEPLOYMENT.md
@@ -211,6 +211,67 @@ cd /opt/workdock
|
|||||||
RUN_DJANGO_CHECK=1 ./scripts/deploy_stack.sh .env.prod docker-compose.prod.yml
|
RUN_DJANGO_CHECK=1 ./scripts/deploy_stack.sh .env.prod docker-compose.prod.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Runtime config sync
|
||||||
|
Deployment updates code. It does not automatically overwrite runtime database configuration.
|
||||||
|
|
||||||
|
Use explicit sync when you want local configuration to be compared or applied to another environment.
|
||||||
|
|
||||||
|
### Supported sync scopes
|
||||||
|
- `PortalAppConfig`
|
||||||
|
- `PortalBranding`
|
||||||
|
- `PortalCompanyConfig`
|
||||||
|
|
||||||
|
### Step 1: export locally
|
||||||
|
```bash
|
||||||
|
docker compose exec -T web python manage.py export_portal_app_config --output /tmp/portal-app-config.json
|
||||||
|
docker compose exec -T web python manage.py export_portal_deployment_config --output /tmp/portal-deployment-config.json
|
||||||
|
docker compose cp web:/tmp/portal-app-config.json /tmp/portal-app-config.json
|
||||||
|
docker compose cp web:/tmp/portal-deployment-config.json /tmp/portal-deployment-config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: copy JSON files to the server host
|
||||||
|
```bash
|
||||||
|
scp -4 /tmp/portal-app-config.json /tmp/portal-deployment-config.json root@192.168.2.55:/opt/workdock/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: copy JSON files into the running web container
|
||||||
|
The server uses baked images, not a bind-mounted app tree. Because of that, the running `web` container cannot automatically read arbitrary files from `/opt/workdock`.
|
||||||
|
|
||||||
|
Use:
|
||||||
|
```bash
|
||||||
|
ssh -4 root@192.168.2.55 '
|
||||||
|
docker cp /opt/workdock/portal-app-config.json workdock-web-1:/tmp/portal-app-config.json &&
|
||||||
|
docker cp /opt/workdock/portal-deployment-config.json workdock-web-1:/tmp/portal-deployment-config.json
|
||||||
|
'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: dry-run the import on the server
|
||||||
|
```bash
|
||||||
|
ssh -4 root@192.168.2.55 '
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json --dry-run &&
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json --dry-run
|
||||||
|
'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: apply the import on the server
|
||||||
|
Only do this if the dry run looks correct.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh -4 root@192.168.2.55 '
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json &&
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json
|
||||||
|
'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- `PortalAppConfig` covers app order, section, visibility, and overrides.
|
||||||
|
- deployment-config sync covers branding/company text and metadata.
|
||||||
|
- uploaded branding files are intentionally excluded:
|
||||||
|
- logo
|
||||||
|
- favicon
|
||||||
|
- PDF letterhead
|
||||||
|
- use dry-run first. Treat config sync as an explicit operator action, not something hidden inside deploy.
|
||||||
|
|
||||||
## GitHub Actions workflows
|
## GitHub Actions workflows
|
||||||
### Test deployment workflow
|
### Test deployment workflow
|
||||||
File:
|
File:
|
||||||
@@ -406,6 +467,65 @@ For production, you may later want image-tag based rollback. That is not necessa
|
|||||||
- test deployment is intentionally weaker than production on transport security
|
- test deployment is intentionally weaker than production on transport security
|
||||||
- production should not reuse the test env model
|
- production should not reuse the test env model
|
||||||
|
|
||||||
|
## Command reference
|
||||||
|
Use this as the short operational index.
|
||||||
|
|
||||||
|
### Local development
|
||||||
|
```bash
|
||||||
|
docker compose up -d --build
|
||||||
|
```
|
||||||
|
Start or rebuild the local stack.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose restart web
|
||||||
|
docker compose restart worker
|
||||||
|
```
|
||||||
|
Restart the app services after code/template changes.
|
||||||
|
|
||||||
|
### Validation
|
||||||
|
```bash
|
||||||
|
docker compose exec -T web python manage.py check
|
||||||
|
```
|
||||||
|
Run Django system checks.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec -T web python manage.py test
|
||||||
|
```
|
||||||
|
Run the full test suite.
|
||||||
|
|
||||||
|
### Local test deployment
|
||||||
|
```bash
|
||||||
|
./scripts/deploy_test_from_mac.sh
|
||||||
|
```
|
||||||
|
Sync the current `develop` checkout to the LAN test server and deploy it.
|
||||||
|
|
||||||
|
### Direct server deployment
|
||||||
|
```bash
|
||||||
|
cd /opt/workdock
|
||||||
|
RUN_DJANGO_CHECK=0 DEPLOY_HEALTH_URL="http://127.0.0.1:8088/healthz/" ./scripts/deploy_stack.sh .env.test docker-compose.prod.yml
|
||||||
|
```
|
||||||
|
Deploy when code is already present on the server.
|
||||||
|
|
||||||
|
### Config export/import
|
||||||
|
```bash
|
||||||
|
docker compose exec -T web python manage.py export_portal_app_config --output /tmp/portal-app-config.json
|
||||||
|
docker compose exec -T web python manage.py export_portal_deployment_config --output /tmp/portal-deployment-config.json
|
||||||
|
```
|
||||||
|
Export runtime configuration from local.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json --dry-run
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json --dry-run
|
||||||
|
```
|
||||||
|
Validate server-side config import before applying it.
|
||||||
|
|
||||||
|
### Backup
|
||||||
|
```bash
|
||||||
|
make backup-create
|
||||||
|
make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS
|
||||||
|
```
|
||||||
|
Create and verify backup bundles.
|
||||||
|
|
||||||
## Current known-good state
|
## Current known-good state
|
||||||
Validated manually:
|
Validated manually:
|
||||||
- repository pushed to private GitHub
|
- repository pushed to private GitHub
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
<a href="#hosts">Hosts & Domains</a>
|
<a href="#hosts">Hosts & Domains</a>
|
||||||
<a href="#cicd">CI/CD</a>
|
<a href="#cicd">CI/CD</a>
|
||||||
<a href="#deploy">Deployment</a>
|
<a href="#deploy">Deployment</a>
|
||||||
|
<a href="#commands">Commands</a>
|
||||||
<a href="#troubleshooting">Troubleshooting</a>
|
<a href="#troubleshooting">Troubleshooting</a>
|
||||||
<a href="#security">Security</a>
|
<a href="#security">Security</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -484,6 +485,39 @@ RUN_DJANGO_CHECK=0 DEPLOY_HEALTH_URL="http://127.0.0.1:8088/healthz/" ./scripts/
|
|||||||
<h3>Validation after deploy</h3>
|
<h3>Validation after deploy</h3>
|
||||||
<pre><code>curl -I http://192.168.2.55:8088/healthz/
|
<pre><code>curl -I http://192.168.2.55:8088/healthz/
|
||||||
ssh root@192.168.2.55 "cd /opt/workdock && docker compose --env-file .env.test -f docker-compose.prod.yml ps"</code></pre>
|
ssh root@192.168.2.55 "cd /opt/workdock && docker compose --env-file .env.test -f docker-compose.prod.yml ps"</code></pre>
|
||||||
|
<h3>Runtime config sync</h3>
|
||||||
|
<p>Deployment updates code. It does not automatically overwrite runtime database configuration. Use explicit sync when you want local configuration compared or applied to the server.</p>
|
||||||
|
<p>Supported sync scopes:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>PortalAppConfig</code></li>
|
||||||
|
<li><code>PortalBranding</code></li>
|
||||||
|
<li><code>PortalCompanyConfig</code></li>
|
||||||
|
</ul>
|
||||||
|
<p>Export locally:</p>
|
||||||
|
<pre><code>docker compose exec -T web python manage.py export_portal_app_config --output /tmp/portal-app-config.json
|
||||||
|
docker compose exec -T web python manage.py export_portal_deployment_config --output /tmp/portal-deployment-config.json
|
||||||
|
docker compose cp web:/tmp/portal-app-config.json /tmp/portal-app-config.json
|
||||||
|
docker compose cp web:/tmp/portal-deployment-config.json /tmp/portal-deployment-config.json</code></pre>
|
||||||
|
<p>Copy the JSON files to the server host:</p>
|
||||||
|
<pre><code>scp -4 /tmp/portal-app-config.json /tmp/portal-deployment-config.json root@192.168.2.55:/opt/workdock/</code></pre>
|
||||||
|
<p>Because the server runs baked container images instead of a bind-mounted app tree, copy the files into the running web container before importing:</p>
|
||||||
|
<pre><code>ssh -4 root@192.168.2.55 '
|
||||||
|
docker cp /opt/workdock/portal-app-config.json workdock-web-1:/tmp/portal-app-config.json &&
|
||||||
|
docker cp /opt/workdock/portal-deployment-config.json workdock-web-1:/tmp/portal-deployment-config.json
|
||||||
|
'</code></pre>
|
||||||
|
<p>Dry-run the import first:</p>
|
||||||
|
<pre><code>ssh -4 root@192.168.2.55 '
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json --dry-run &&
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json --dry-run
|
||||||
|
'</code></pre>
|
||||||
|
<p>Only apply the import after the dry run looks correct:</p>
|
||||||
|
<pre><code>ssh -4 root@192.168.2.55 '
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json &&
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json
|
||||||
|
'</code></pre>
|
||||||
|
<div class="note">
|
||||||
|
Uploaded branding assets such as logo, favicon, and PDF letterhead are intentionally not included in deployment-config sync. They remain explicit media assets.
|
||||||
|
</div>
|
||||||
<h3>Proxmox / LXC requirement</h3>
|
<h3>Proxmox / LXC requirement</h3>
|
||||||
<p>The current server is an Ubuntu CT on Proxmox running Docker inside the container. The CT required Proxmox-side configuration before Docker containers could start correctly.</p>
|
<p>The current server is an Ubuntu CT on Proxmox running Docker inside the container. The CT required Proxmox-side configuration before Docker containers could start correctly.</p>
|
||||||
<pre><code>features: nesting=1,keyctl=1
|
<pre><code>features: nesting=1,keyctl=1
|
||||||
@@ -511,7 +545,50 @@ lxc.mount.entry: /dev/null sys/module/apparmor/parameters/enabled none bind 0 0<
|
|||||||
<li>Take a snapshot commit before major next-phase work</li>
|
<li>Take a snapshot commit before major next-phase work</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h2 id="troubleshooting">19) Troubleshooting</h2>
|
<h2 id="commands">19) Command Reference</h2>
|
||||||
|
<div class="box">
|
||||||
|
<h3>Local development</h3>
|
||||||
|
<pre><code>docker compose up -d --build</code></pre>
|
||||||
|
<p>Start or rebuild the local stack.</p>
|
||||||
|
<pre><code>docker compose restart web
|
||||||
|
docker compose restart worker</code></pre>
|
||||||
|
<p>Restart app services after code or template changes.</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3>Validation</h3>
|
||||||
|
<pre><code>docker compose exec -T web python manage.py check</code></pre>
|
||||||
|
<p>Run Django system checks.</p>
|
||||||
|
<pre><code>docker compose exec -T web python manage.py test</code></pre>
|
||||||
|
<p>Run the full test suite.</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3>Local test deployment</h3>
|
||||||
|
<pre><code>./scripts/deploy_test_from_mac.sh</code></pre>
|
||||||
|
<p>Sync the current <code>develop</code> checkout to the LAN test server and deploy it.</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3>Direct server deployment</h3>
|
||||||
|
<pre><code>cd /opt/workdock
|
||||||
|
RUN_DJANGO_CHECK=0 DEPLOY_HEALTH_URL="http://127.0.0.1:8088/healthz/" ./scripts/deploy_stack.sh .env.test docker-compose.prod.yml</code></pre>
|
||||||
|
<p>Deploy when code is already present on the server.</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3>Config sync</h3>
|
||||||
|
<pre><code>docker compose exec -T web python manage.py export_portal_app_config --output /tmp/portal-app-config.json
|
||||||
|
docker compose exec -T web python manage.py export_portal_deployment_config --output /tmp/portal-deployment-config.json</code></pre>
|
||||||
|
<p>Export runtime configuration from local.</p>
|
||||||
|
<pre><code>docker exec workdock-web-1 python manage.py import_portal_app_config /tmp/portal-app-config.json --dry-run
|
||||||
|
docker exec workdock-web-1 python manage.py import_portal_deployment_config /tmp/portal-deployment-config.json --dry-run</code></pre>
|
||||||
|
<p>Validate server-side config import before applying it.</p>
|
||||||
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h3>Backup</h3>
|
||||||
|
<pre><code>make backup-create
|
||||||
|
make backup-verify BACKUP_DIR=backups/backup_YYYYmmdd_HHMMSS</code></pre>
|
||||||
|
<p>Create and verify backup bundles.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="troubleshooting">20) Troubleshooting</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Page looks stale:</strong> restart <code>web</code> and hard-refresh browser</li>
|
<li><strong>Page looks stale:</strong> restart <code>web</code> and hard-refresh browser</li>
|
||||||
<li><strong>Second request hangs:</strong> inspect web logs and verify health endpoint</li>
|
<li><strong>Second request hangs:</strong> inspect web logs and verify health endpoint</li>
|
||||||
@@ -522,7 +599,7 @@ lxc.mount.entry: /dev/null sys/module/apparmor/parameters/enabled none bind 0 0<
|
|||||||
<li><strong>Requests dependency warning appears:</strong> verify <code>chardet==5.2.0</code> is installed in the rebuilt image and restart <code>web</code>/<code>worker</code></li>
|
<li><strong>Requests dependency warning appears:</strong> verify <code>chardet==5.2.0</code> is installed in the rebuilt image and restart <code>web</code>/<code>worker</code></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 id="security">20) Security and Maintenance Notes</h2>
|
<h2 id="security">21) Security and Maintenance Notes</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Containers run as non-root <code>app</code> user.</li>
|
<li>Containers run as non-root <code>app</code> user.</li>
|
||||||
<li>Keep secrets in <code>.env</code>, not in tracked files.</li>
|
<li>Keep secrets in <code>.env</code>, not in tracked files.</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user