8 months ago
I am facing an issue with Railway Storage Buckets where uploads (PutObject) are consistently failing with AccessDenied, even though the setup appears to be correct.
What I have already verified:
- The bucket is correctly connected to the Django backend service using “Connect Service to Bucket”
- All bucket variables are injected automatically via Railway references (no manual credentials)
- Bucket and service are in the same environment
- Environment variables are present and correctly mapped:
AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_ENDPOINT_URLAWS_S3_BUCKET_NAMEAWS_DEFAULT_REGION
- Using
boto3directly (outside Django) with the injected credentials also results in:
botocore.exceptions.ClientError: AccessDenied when calling PutObject - Signed URLs previously worked for reading, but writing has never succeeded
- This confirms the issue is not Django, not django-storages, and not application logic
Expected behavior:
The service-bound credentials should allow s3:PutObject on the connected bucket, as described in the Storage Buckets documentation.
Actual behavior:
All write attempts fail with AccessDenied, even with correct service binding and variable injection.
NOTE:
I am select N/A as it does not showing that bucket service in the service dropdown
1 Replies
2 months ago
The missing setting in the variable list is the bucket's URL style. Railway Buckets can require either virtual-hosted style or, for some older buckets, path style. A correctly signed request using the wrong addressing style can return AccessDenied, even though the access key, secret, endpoint, and bucket name are all present.
Open the bucket's Credentials tab and note the exact URL style shown there. Then pass that value explicitly to botocore:
import os
import boto3
from botocore.config import Config
url_style = os.getenv("AWS_S3_URL_STYLE", "virtual")
bucket_name = os.getenv("AWS_S3_BUCKET_NAME") or os.environ["BUCKET"]
s3 = boto3.client(
"s3",
endpoint_url=os.environ["AWS_ENDPOINT_URL"],
aws_access_key_id=os.environ["AWS_ACCESS_KEY_ID"],
aws_secret_access_key=os.environ["AWS_SECRET_ACCESS_KEY"],
region_name=os.getenv("AWS_DEFAULT_REGION", "auto"),
config=Config(
signature_version="s3v4",
s3={"addressing_style": url_style},
),
)
s3.put_object(
Bucket=bucket_name,
Key="railway-write-test.txt",
Body=b"ok\n",
ContentType="text/plain",
)Railway's current bucket documentation says that buckets use the URL style shown in the Credentials tab and that older buckets may require path style. The CLI credential output also includes AWS_S3_URL_STYLE:
- https://docs.railway.com/storage-buckets#url-style
- https://docs.railway.com/cli/bucket#show-or-reset-credentials
Two other details are important:
- Use the globally unique S3 API bucket name (
AWS_S3_BUCKET_NAMEorBUCKET), not the human-readableRAILWAY_BUCKET_NAME. - Use credentials from the same Railway environment as the bucket. Each environment has an isolated bucket instance and its own credentials.
After adding AWS_S3_URL_STYLE to the service variables, redeploy before testing so the running container receives it. Do not paste the access key, secret key, presigned URL, or a debug log containing the Authorization header into this public thread.
If this exact one-object test still returns AccessDenied, post only the selected URL style, environment name, bucket region, botocore version, and the request host with the bucket name redacted. That is enough to distinguish an addressing/signing problem from a Railway-side credential-policy problem.