Skip to content
Draft
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
76 changes: 76 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# AGENTS.md

## Cursor Cloud specific instructions

This repo is the **Shipyard Flask template**: a Flask + Celery app backed by
PostgreSQL, Redis, and LocalStack (emulated S3). Everything runs through Docker
Compose — there is no supported non-Docker "run locally" path. Docker itself is
already installed in the VM snapshot; the notes below cover the non-obvious
caveats for running it here.

### Services (all defined in `docker-compose.yaml`)

| Service | Required | Notes |
| --- | --- | --- |
| `web` | yes | Flask app on `http://localhost:8080`. With `DEV=true` it runs the Flask dev server (auto-reload); otherwise uWSGI. |
| `postgres` | yes | `web`/`worker` block on it at startup. Data lives in the `workspace_postgres` named volume. |
| `redis` | yes | Celery broker + result backend. |
| `worker` | optional | Celery worker + Beat; runs the `do_something` heartbeat every 5s (increments counters by 10). App serves pages without it. |
| `localstack` | optional | Emulated S3 for the `/files` routes only. Pinned to a free Community version (see below). |

### Starting everything (non-obvious startup steps)

- **The Docker daemon is NOT auto-started** (no systemd). Start it once per VM
session before any docker command, e.g. in a tmux session:
`sudo dockerd > /tmp/dockerd.log 2>&1 &` then wait a few seconds.
- **Use `sudo` for docker** (`sudo docker ...`, `sudo docker compose ...`). The
`ubuntu` user is in the `docker` group, but the group is not active in fresh
non-login shells, so the socket returns "permission denied" without sudo.
- Preserve the `DEV` env var through sudo: `DEV=true sudo -E docker compose ...`.
Set `DEV=true` for the auto-reloading dev server.
- The `Makefile` calls the v1 `docker-compose` command; only the v2 plugin is
installed, so a `/usr/local/bin/docker-compose -> docker compose` shim is used.
`make` targets still won't have docker group access, so prefer the compose
commands directly (with `sudo -E`) over `make develop`.

Bring-up sequence (mirrors `make develop`):
```
DEV=true sudo -E docker compose up -d postgres
sudo -E docker compose exec -T postgres sh -c 'while ! nc -z postgres 5432; do sleep 0.2; done'
DEV=true sudo -E docker compose run --rm worker poetry run flask db upgrade # migrations
DEV=true sudo -E docker compose up -d # all services
```

### Hello-world / smoke check

- Counter page (increments a DB-backed counter): `curl http://localhost:8080/`
- **S3 files flow order matters**: `GET /files` creates the `test-bucket` and
lists it; `GET /files/create` uploads an object but does NOT create the bucket,
so hit `/files` first or `/files/create` returns 500 (`NoSuchBucket`).

### Migrations & tests

- Migrations: `... docker compose run --rm worker poetry run flask db upgrade`.
Postgres data persists in the `workspace_postgres` volume; if that volume is
reset you must re-run migrations.
- Tests: pytest is installed but the template ships **no tests** (`pytest` reports
"no tests ran"). Run with `... docker compose run --rm worker poetry run pytest`.

### Dependency notes (already handled in the committed `Dockerfile` / compose)

These pins exist because the template targets ancient runtimes (Python 3.8 on
Alpine 3.11) and its original unpinned dependencies no longer resolve today. If
you change `pyproject.toml`, `poetry.lock`, `Dockerfile`, or the compose file,
rebuild with `DEV=true sudo -E docker compose build` (the update script does not
rebuild images).

- Poetry is pinned to `1.1.15` (matches `poetry.lock` lock-version 1.1 and avoids
modern Poetry's `rapidfuzz` source build on musl).
- `cryptography==2.8` is pre-installed so Poetry's keyring dep doesn't pull the
latest cryptography (needs Rust / `setuptools>=77`, impossible on py3.8).
- `poetry config virtualenvs.create false` installs into the system interpreter;
the venv Poetry seeds ships a setuptools too new to build the 2020-era sdists
(psycopg2 2.8.4, uwsgi 2.0.18, SQLAlchemy-Utils). `/home/app/.local/bin` is
added to `PATH` so `poetry run flask`/`celery` find their console scripts.
- LocalStack is pinned to `localstack/localstack:3.8.1`; the unpinned/`latest`
tag now requires a paid `LOCALSTACK_AUTH_TOKEN` and exits (code 55) without one.
21 changes: 20 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,14 @@ RUn apk add --update --no-cache \
#cd .. && rm -rf watchman

# Install poetry
RUN pip install poetry
# Pin to 1.1.x to match the committed poetry.lock (lock-version 1.1) and avoid
# pulling in modern Poetry's rapidfuzz dependency, which has no musllinux wheel
# for cp38 and otherwise compiles from source (very slow) on this Alpine base.
# Pre-install cryptography 2.8 (the version pinned in poetry.lock, builds against
# the openssl-dev already installed above) so that Poetry's keyring/SecretStorage
# dependency does not pull the latest cryptography, which needs setuptools>=77 and
# Rust and cannot build on this Python 3.8 image.
RUN pip install "cryptography==2.8" && pip install "poetry==1.1.15"

# Create an app user, prepare permissions, and run as the user
RUN adduser -S app
Expand All @@ -39,6 +46,12 @@ RUN mkdir -p /var/run/celery && \
USER app

# Install Python dependencies
# Install into the system interpreter instead of a Poetry-managed virtualenv.
# The virtualenv that Poetry seeds ships a newer setuptools whose
# canonicalize_version(strip_trailing_zero=...) call cannot build the 2020-era
# sdists pinned here (psycopg2 2.8.4, uwsgi 2.0.18, SQLAlchemy-Utils 0.36.3),
# whereas the base image's older system setuptools builds them fine.
RUN poetry config virtualenvs.create false
ADD pyproject.toml poetry.lock ./
RUN poetry install

Expand All @@ -51,6 +64,12 @@ RUN poetry install
# Add system files
ADD filesystem /

# With virtualenvs.create=false and a non-root user, Poetry installs packages
# (and their console scripts such as flask/celery/uwsgi) into the user site at
# /home/app/.local, which is not on PATH by default. Add it so `poetry run flask`
# and the entrypoint scripts can find these executables.
ENV PATH="/home/app/.local/bin:${PATH}"

# Set the default command
ENV FLASK_APP src/entry:flask_app
CMD /entrypoints/web.sh
5 changes: 4 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ services:
- '6379'

localstack:
image: 'localstack/localstack'
# Pinned to a free Community edition release. The unpinned/`latest` tag now
# resolves to a build that requires a paid LOCALSTACK_AUTH_TOKEN and exits
# (code 55) without one, which breaks the S3 (/files) demo flow.
image: 'localstack/localstack:3.8.1'
ports:
- '4566:4566'
- '4571:4571'
Expand Down