Packaging and Deploying the Full Stack

software-engineering
full-stack
packaging
deployment
releases
security
Ship Python code, browser assets, configuration, database compatibility, and an opt-in update path as one release unit.

A development server is not a release. The shipped unit must contain the CLI, FastAPI application, browser assets, agent-runner adapters, migration compatibility, configuration contract, and rollback evidence. This chapter builds and inspects that unit, then treats updates and deployment as changes to the complete stack rather than only the Python package version.

Release every layer that one feature needs

Autocode serves the frontend from package data, so one wheel contains Python modules and web/index.html, web/app.css, and web/app.js. The service extra installs FastAPI and Uvicorn; autocode serve composes the database and agent mode from explicit configuration. The container installs that same wheel-shaped project and runs the same application factory.

Track code version, REST and WebSocket protocol version, persisted-data version, and artifact schema version separately. A patch release must not silently make old sessions unreadable or leave a cached browser speaking an incompatible event protocol.

from autocode.cli import VERSION
from autocode.updates import version_key
from autocode_service.api import STATIC_DIR

assert VERSION == "0.2.0"
assert version_key(VERSION) == (0, 2, 0)
assert version_key("0.3.0") > version_key(VERSION)
assert {path.name for path in STATIC_DIR.iterdir()} >= {"index.html", "app.css", "app.js"}
print("release:", VERSION, "browser assets:", sorted(path.name for path in STATIC_DIR.iterdir()))
release: 0.2.0 browser assets: ['app.css', 'app.js', 'index.html']

A clean-install rehearsal builds the wheel, installs it outside the development checkout with the service extra, runs autocode doctor, starts autocode serve against a temporary data directory, loads /, creates a session through REST, and completes one WebSocket run. Inspect the wheel contents before publishing so missing static package data fails the release rather than the first browser request.

Update checks and configuration are network behavior

An update check is an external request and needs consent, a documented payload, bounded timeouts, and an off switch. Runtime configuration also crosses trust boundaries: AUTOCODE_DATA_DIR selects durable state and AUTOCODE_AGENT_MODE selects deterministic or live model behavior. Secrets belong in environment or managed injection, never in browser assets or image layers.

from autocode.updates import UpdateChecker, UpdateManifest

requests = []
disabled = UpdateChecker("0.1.0", enabled=False)
assert disabled.check(lambda: requests.append("called") or UpdateManifest("0.2.0", "notes")) is None
assert requests == []

enabled = UpdateChecker("0.1.0", enabled=True)
update = enabled.check(lambda: UpdateManifest("0.2.0", "notes"))
assert update is not None
assert update.notes_url == "notes"
print("opt-in update:", update.version)
opt-in update: 0.2.0

The disabled checker performs zero calls because it never evaluates the fetch function. Test this with a request spy at the service boundary; a settings checkbox is not sufficient evidence if an imported library starts telemetry or update work elsewhere.

The container is an adapter around the same application

The Dockerfile installs autocode[service], exposes port 8000, and runs the FastAPI factory with Uvicorn. Compose mounts /data and sets AUTOCODE_DATA_DIR=/data, so replacing the container does not replace SQLite state. The default image stays in deterministic agent mode; live credentials must be injected at runtime.

A production deployment still needs TLS termination, origin and host policy, managed identity, persistent database and artifact services, resource limits, health probes, backup ownership, and a migration job. The local image proves packaging and startup, not those external controls.

from pathlib import Path

quickstart = Path("projects/autocode/docs/quickstart.md")
dockerfile = Path("projects/autocode/deploy/Dockerfile")
compose = Path("projects/autocode/deploy/compose.yaml")

for path in [quickstart, dockerfile, compose]:
    assert path.exists()
text = quickstart.read_text(encoding="utf-8")
for required in ["autocode serve", "127.0.0.1:8000", "journal", "backup", "harness"]:
    assert required in text
assert "AUTOCODE_DATA_DIR" in compose.read_text(encoding="utf-8")
assert "autocode_service.api:create_app" in dockerfile.read_text(encoding="utf-8")
print("deployment artifacts:", [path.name for path in [quickstart, dockerfile, compose]])
deployment artifacts: ['quickstart.md', 'Dockerfile', 'compose.yaml']

Documentation and deployment configuration are executable interfaces. The release test should follow the quickstart literally and record the exact built artifact hashes, startup command, HTTP/WebSocket smoke results, database version, and rollback command. A rollback that has not reopened representative data is an untested hypothesis.

Exercises

Write a clean-install release rehearsal for version N+1. Include wheel contents, browser asset loading, REST and WebSocket smoke requests, migration and downgrade on copied data, container persistence, update-check consent, and the owner of rollback.

[P10.1] Rehearse a full-stack upgrade

List the steps for opening version N data in version N+1, serving the new browser/API protocol, and proving that code, static assets, persisted data, and the container can all roll back coherently.

Back to top